diff options
| author | Felipe Balbi <[email protected]> | 2025-11-07 11:00:15 -0800 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-11-07 11:00:15 -0800 |
| commit | 5632acec18cc5906b1625a8facf530db56c73300 (patch) | |
| tree | abf2897f4b2f9814069c64611896be2d42cf2ce8 | |
| parent | 47e383545f4aac3bfaec0563429cc721540e665a (diff) | |
| parent | 9590d94ee9ba016f65a13100c429fc56ffe58e40 (diff) | |
Merge pull request #1 from bogdan-petru/import/mcxa276-initial
feat(mcxa276): initial HAL import
61 files changed, 8157 insertions, 244 deletions
diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 000000000..3286be01c --- /dev/null +++ b/.cargo/config.toml | |||
| @@ -0,0 +1,19 @@ | |||
| 1 | [build] | ||
| 2 | target = "thumbv8m.main-none-eabihf" | ||
| 3 | |||
| 4 | [target.thumbv8m.main-none-eabihf] | ||
| 5 | runner = "./run.sh" | ||
| 6 | # Use our custom ram.ld for RAM-execution | ||
| 7 | rustflags = ["-C", "link-arg=-Tdefmt.x", "-C", "link-arg=-Tram.ld"] | ||
| 8 | |||
| 9 | [alias] | ||
| 10 | # Build examples with defmt+RTT and LPUART2 enabled | ||
| 11 | build-defmt = ["build", "--features", "defmt defmt-rtt lpuart2", "--examples"] | ||
| 12 | # Blocking run (uses run.sh). Good for one-terminal flash+run. | ||
| 13 | run-defmt = ["run", "--features", "defmt defmt-rtt lpuart2", "--example", "hello"] | ||
| 14 | # Non-blocking flash to RAM via J-Link; frees probe for RTT attach | ||
| 15 | flash-nb = ["!./tools/run_jlink_noblock.sh", "target/thumbv8m.main-none-eabihf/debug/examples/hello", "1366:0101:000600110607", "MCXA276", "500"] | ||
| 16 | # Attach-only viewer that decodes defmt over RTT using J-Link | ||
| 17 | # Requires PROBE_RS_PROBE exported (or it will prompt) | ||
| 18 | defmt-attach = ["embed", "--features", "defmt defmt-rtt lpuart2", "--example", "hello"] | ||
| 19 | |||
diff --git a/.github/DOCS.md b/.github/DOCS.md new file mode 100644 index 000000000..e932784c7 --- /dev/null +++ b/.github/DOCS.md | |||
| @@ -0,0 +1,23 @@ | |||
| 1 | # Github config and workflows | ||
| 2 | |||
| 3 | In this folder there is configuration for codecoverage, dependabot, and ci | ||
| 4 | workflows that check the library more deeply than the default configurations. | ||
| 5 | |||
| 6 | This folder can be or was merged using a --allow-unrelated-histories merge | ||
| 7 | strategy from <https://github.com/jonhoo/rust-ci-conf/> which provides a | ||
| 8 | reasonably sensible base for writing your own ci on. By using this strategy | ||
| 9 | the history of the CI repo is included in your repo, and future updates to | ||
| 10 | the CI can be merged later. | ||
| 11 | |||
| 12 | To perform this merge run: | ||
| 13 | |||
| 14 | ```shell | ||
| 15 | git remote add ci https://github.com/jonhoo/rust-ci-conf.git | ||
| 16 | git fetch ci | ||
| 17 | git merge --allow-unrelated-histories ci/main | ||
| 18 | ``` | ||
| 19 | |||
| 20 | An overview of the files in this project is available at: | ||
| 21 | <https://www.youtube.com/watch?v=xUH-4y92jPg&t=491s>, which contains some | ||
| 22 | rationale for decisions and runs through an example of solving minimal version | ||
| 23 | and OpenSSL issues. | ||
diff --git a/.github/codecov.yml b/.github/codecov.yml new file mode 100644 index 000000000..cd5ce8fc1 --- /dev/null +++ b/.github/codecov.yml | |||
| @@ -0,0 +1,21 @@ | |||
| 1 | # ref: https://docs.codecov.com/docs/codecovyml-reference | ||
| 2 | coverage: | ||
| 3 | # Hold ourselves to a high bar | ||
| 4 | range: 85..100 | ||
| 5 | round: down | ||
| 6 | precision: 1 | ||
| 7 | status: | ||
| 8 | # ref: https://docs.codecov.com/docs/commit-status | ||
| 9 | project: | ||
| 10 | default: | ||
| 11 | # Avoid false negatives | ||
| 12 | threshold: 1% | ||
| 13 | |||
| 14 | # Test files aren't important for coverage | ||
| 15 | ignore: | ||
| 16 | - "tests" | ||
| 17 | |||
| 18 | # Make comments less noisy | ||
| 19 | comment: | ||
| 20 | layout: "files" | ||
| 21 | require_changes: true | ||
diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..d0f091e7b --- /dev/null +++ b/.github/dependabot.yml | |||
| @@ -0,0 +1,19 @@ | |||
| 1 | version: 2 | ||
| 2 | updates: | ||
| 3 | - package-ecosystem: github-actions | ||
| 4 | directory: / | ||
| 5 | schedule: | ||
| 6 | interval: daily | ||
| 7 | - package-ecosystem: cargo | ||
| 8 | directory: / | ||
| 9 | schedule: | ||
| 10 | interval: daily | ||
| 11 | ignore: | ||
| 12 | - dependency-name: "*" | ||
| 13 | # patch and minor updates don't matter for libraries as consumers of this library build | ||
| 14 | # with their own lockfile, rather than the version specified in this library's lockfile | ||
| 15 | # remove this ignore rule if your package has binaries to ensure that the binaries are | ||
| 16 | # built with the exact set of dependencies and those are up to date. | ||
| 17 | update-types: | ||
| 18 | - "version-update:semver-patch" | ||
| 19 | - "version-update:semver-minor" | ||
diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 9bf402d61..1a09a1492 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml | |||
| @@ -1,6 +1,5 @@ | |||
| 1 | # This workflow runs whenever a PR is opened or updated, or a commit is pushed to main. It runs | 1 | # This workflow runs whenever a PR is opened or updated, or a commit is pushed to main. It runs |
| 2 | # several checks: | 2 | # several checks: |
| 3 | # - commit_list: produces a list of commits to be checked | ||
| 4 | # - fmt: checks that the code is formatted according to rustfmt | 3 | # - fmt: checks that the code is formatted according to rustfmt |
| 5 | # - clippy: checks that the code does not contain any clippy warnings | 4 | # - clippy: checks that the code does not contain any clippy warnings |
| 6 | # - doc: checks that the code can be documented without errors | 5 | # - doc: checks that the code can be documented without errors |
| @@ -8,66 +7,84 @@ | |||
| 8 | # - msrv: check that the msrv specified in the crate is correct | 7 | # - msrv: check that the msrv specified in the crate is correct |
| 9 | permissions: | 8 | permissions: |
| 10 | contents: read | 9 | contents: read |
| 10 | |||
| 11 | # This configuration allows maintainers of this repo to create a branch and pull request based on | 11 | # This configuration allows maintainers of this repo to create a branch and pull request based on |
| 12 | # the new branch. Restricting the push trigger to the main branch ensures that the PR only gets | 12 | # the new branch. Restricting the push trigger to the main branch ensures that the PR only gets |
| 13 | # built once. | 13 | # built once. |
| 14 | on: | 14 | on: |
| 15 | |||
| 15 | push: | 16 | push: |
| 16 | branches: [main] | 17 | branches: [main, main-nextgen] |
| 17 | pull_request: | 18 | pull_request: |
| 19 | |||
| 18 | # If new code is pushed to a PR branch, then cancel in progress workflows for that PR. Ensures that | 20 | # If new code is pushed to a PR branch, then cancel in progress workflows for that PR. Ensures that |
| 19 | # we don't waste CI time, and returns results quicker https://github.com/jonhoo/rust-ci-conf/pull/5 | 21 | # we don't waste CI time, and returns results quicker https://github.com/jonhoo/rust-ci-conf/pull/5 |
| 20 | concurrency: | 22 | concurrency: |
| 21 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} | 23 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} |
| 22 | cancel-in-progress: true | 24 | cancel-in-progress: true |
| 25 | |||
| 23 | name: check | 26 | name: check |
| 27 | |||
| 24 | jobs: | 28 | jobs: |
| 29 | |||
| 25 | fmt: | 30 | fmt: |
| 26 | runs-on: ubuntu-latest | 31 | runs-on: ubuntu-latest |
| 27 | name: stable / fmt | 32 | name: nightly / fmt |
| 33 | |||
| 34 | strategy: | ||
| 35 | fail-fast: false | ||
| 36 | matrix: | ||
| 37 | workdir: [ ".", "examples/rt633", "examples/rt685s-evk",] | ||
| 38 | |||
| 28 | steps: | 39 | steps: |
| 29 | - uses: actions/checkout@v4 | 40 | - uses: actions/checkout@v4 |
| 30 | with: | 41 | with: |
| 31 | submodules: true | 42 | submodules: true |
| 32 | - name: Install stable | 43 | |
| 33 | uses: dtolnay/rust-toolchain@stable | 44 | - name: Install nightly |
| 45 | uses: dtolnay/rust-toolchain@nightly | ||
| 34 | with: | 46 | with: |
| 35 | components: rustfmt | 47 | components: rustfmt |
| 48 | |||
| 36 | - name: cargo fmt --check | 49 | - name: cargo fmt --check |
| 37 | run: cargo fmt --check | 50 | run: cargo fmt --check |
| 51 | working-directory: ${{ matrix.workdir }} | ||
| 38 | 52 | ||
| 39 | clippy: | 53 | clippy-examples: |
| 40 | runs-on: ubuntu-latest | 54 | runs-on: ubuntu-latest |
| 41 | name: ${{ matrix.toolchain }} / clippy | 55 | name: ${{ matrix.toolchain }} / clippy |
| 56 | |||
| 42 | permissions: | 57 | permissions: |
| 43 | contents: read | 58 | contents: read |
| 44 | checks: write | 59 | checks: write |
| 60 | |||
| 45 | strategy: | 61 | strategy: |
| 46 | fail-fast: false | 62 | fail-fast: false |
| 47 | matrix: | 63 | matrix: |
| 48 | # Get early warning of new lints which are regularly introduced in beta channels. | 64 | # Get early warning of new lints which are regularly introduced in beta channels. |
| 49 | toolchain: [stable, beta] | 65 | toolchain: [stable] |
| 66 | workdir: ["examples"] | ||
| 67 | |||
| 50 | steps: | 68 | steps: |
| 51 | - uses: actions/checkout@v4 | 69 | - uses: actions/checkout@v4 |
| 52 | with: | 70 | with: |
| 53 | submodules: true | 71 | submodules: true |
| 72 | |||
| 54 | - name: Install ${{ matrix.toolchain }} | 73 | - name: Install ${{ matrix.toolchain }} |
| 55 | uses: dtolnay/rust-toolchain@master | 74 | uses: dtolnay/rust-toolchain@master |
| 56 | with: | 75 | with: |
| 57 | toolchain: ${{ matrix.toolchain }} | 76 | toolchain: ${{ matrix.toolchain }} |
| 58 | components: clippy | 77 | components: clippy |
| 78 | |||
| 59 | - name: cargo clippy | 79 | - name: cargo clippy |
| 60 | uses: giraffate/clippy-action@v1 | 80 | working-directory: ${{ matrix.workdir }} |
| 61 | with: | 81 | run: | |
| 62 | reporter: 'github-pr-check' | 82 | cargo clippy --locked -- -Dwarnings -D clippy::suspicious -D clippy::correctness -D clippy::perf -D clippy::style |
| 63 | github_token: ${{ secrets.GITHUB_TOKEN }} | ||
| 64 | 83 | ||
| 65 | # Enable once we have a released crate | 84 | # Enable once we have a released crate |
| 66 | # semver: | 85 | # semver: |
| 67 | # runs-on: ubuntu-latest | 86 | # runs-on: ubuntu-latest |
| 68 | # name: semver | 87 | # name: semver |
| 69 | # strategy: | ||
| 70 | # fail-fast: false | ||
| 71 | # steps: | 88 | # steps: |
| 72 | # - uses: actions/checkout@v4 | 89 | # - uses: actions/checkout@v4 |
| 73 | # with: | 90 | # with: |
| @@ -85,14 +102,18 @@ jobs: | |||
| 85 | # API be documented as only available in some specific platforms. | 102 | # API be documented as only available in some specific platforms. |
| 86 | runs-on: ubuntu-latest | 103 | runs-on: ubuntu-latest |
| 87 | name: nightly / doc | 104 | name: nightly / doc |
| 105 | |||
| 88 | steps: | 106 | steps: |
| 89 | - uses: actions/checkout@v4 | 107 | - uses: actions/checkout@v4 |
| 90 | with: | 108 | with: |
| 91 | submodules: true | 109 | submodules: true |
| 110 | |||
| 92 | - name: Install nightly | 111 | - name: Install nightly |
| 93 | uses: dtolnay/rust-toolchain@nightly | 112 | uses: dtolnay/rust-toolchain@nightly |
| 113 | |||
| 94 | - name: cargo doc | 114 | - name: cargo doc |
| 95 | run: cargo doc --no-deps --all-features | 115 | run: | |
| 116 | cargo doc --no-deps --all-features --locked | ||
| 96 | env: | 117 | env: |
| 97 | RUSTDOCFLAGS: --cfg docsrs | 118 | RUSTDOCFLAGS: --cfg docsrs |
| 98 | 119 | ||
| @@ -101,16 +122,24 @@ jobs: | |||
| 101 | # which is required for feature unification | 122 | # which is required for feature unification |
| 102 | runs-on: ubuntu-latest | 123 | runs-on: ubuntu-latest |
| 103 | name: ubuntu / stable / features | 124 | name: ubuntu / stable / features |
| 125 | |||
| 126 | strategy: | ||
| 127 | fail-fast: false | ||
| 128 | |||
| 104 | steps: | 129 | steps: |
| 105 | - uses: actions/checkout@v4 | 130 | - uses: actions/checkout@v4 |
| 106 | with: | 131 | with: |
| 107 | submodules: true | 132 | submodules: true |
| 133 | |||
| 108 | - name: Install stable | 134 | - name: Install stable |
| 109 | uses: dtolnay/rust-toolchain@stable | 135 | uses: dtolnay/rust-toolchain@stable |
| 110 | - name: cargo install cargo-hack | 136 | with: |
| 111 | uses: taiki-e/install-action@cargo-hack | 137 | toolchain: stable |
| 112 | # intentionally no target specifier; see https://github.com/jonhoo/rust-ci-conf/pull/4 | 138 | components: clippy |
| 113 | # --feature-powerset runs for every combination of features | 139 | |
| 140 | - name: rustup target add thumbv8m.main-none-eabihf | ||
| 141 | run: rustup target add thumbv8m.main-none-eabihf | ||
| 142 | |||
| 114 | - name: cargo hack | 143 | - name: cargo hack |
| 115 | run: cargo hack --feature-powerset check | 144 | run: cargo hack --feature-powerset check |
| 116 | 145 | ||
| @@ -119,33 +148,22 @@ jobs: | |||
| 119 | # our dependencies. | 148 | # our dependencies. |
| 120 | runs-on: ubuntu-latest | 149 | runs-on: ubuntu-latest |
| 121 | name: ubuntu / stable / deny | 150 | name: ubuntu / stable / deny |
| 151 | |||
| 122 | steps: | 152 | steps: |
| 123 | - uses: actions/checkout@v4 | 153 | - uses: actions/checkout@v4 |
| 124 | with: | 154 | with: |
| 125 | submodules: true | 155 | submodules: true |
| 156 | |||
| 126 | - name: Install stable | 157 | - name: Install stable |
| 127 | uses: dtolnay/rust-toolchain@stable | 158 | uses: dtolnay/rust-toolchain@stable |
| 159 | |||
| 128 | - name: cargo install cargo-deny | 160 | - name: cargo install cargo-deny |
| 129 | uses: EmbarkStudios/cargo-deny-action@v2 | 161 | uses: EmbarkStudios/cargo-deny-action@v2 |
| 130 | with: | 162 | with: |
| 131 | log-level: warn | 163 | log-level: warn |
| 132 | manifest-path: ./Cargo.toml | 164 | manifest-path: ./Cargo.toml |
| 133 | command: check | 165 | command: check |
| 134 | arguments: --all-features | 166 | arguments: --all-features --locked |
| 135 | |||
| 136 | test: | ||
| 137 | runs-on: ubuntu-latest | ||
| 138 | name: ubuntu / stable / test | ||
| 139 | steps: | ||
| 140 | - uses: actions/checkout@v4 | ||
| 141 | with: | ||
| 142 | submodules: true | ||
| 143 | - name: Install stable | ||
| 144 | uses: dtolnay/rust-toolchain@stable | ||
| 145 | - name: cargo install cargo-hack | ||
| 146 | uses: taiki-e/install-action@cargo-hack | ||
| 147 | - name: cargo test | ||
| 148 | run: cargo hack --feature-powerset test | ||
| 149 | 167 | ||
| 150 | msrv: | 168 | msrv: |
| 151 | # check that we can build using the minimal rust version that is specified by this crate | 169 | # check that we can build using the minimal rust version that is specified by this crate |
| @@ -155,15 +173,33 @@ jobs: | |||
| 155 | strategy: | 173 | strategy: |
| 156 | fail-fast: false | 174 | fail-fast: false |
| 157 | matrix: | 175 | matrix: |
| 158 | msrv: ["1.85"] | 176 | msrv: ["1.90"] # We're relying on namespaced-features, which |
| 177 | # was released in 1.60 | ||
| 178 | # | ||
| 179 | # We also depend on `fixed' which requires rust | ||
| 180 | # 1.71 | ||
| 181 | # | ||
| 182 | # Additionally, we depend on embedded-hal-async | ||
| 183 | # which requires 1.75 | ||
| 184 | # | ||
| 185 | # embassy-time requires 1.79 due to | ||
| 186 | # collapse_debuginfo | ||
| 187 | # | ||
| 188 | # embassy upstream switched to rust 1.85 | ||
| 189 | # | ||
| 190 | # unsigned_is_multiple_of requires 1.90, else we get clippy warnings | ||
| 191 | |||
| 159 | name: ubuntu / ${{ matrix.msrv }} | 192 | name: ubuntu / ${{ matrix.msrv }} |
| 160 | steps: | 193 | steps: |
| 161 | - uses: actions/checkout@v4 | 194 | - uses: actions/checkout@v4 |
| 162 | with: | 195 | with: |
| 163 | submodules: true | 196 | submodules: true |
| 197 | |||
| 164 | - name: Install ${{ matrix.msrv }} | 198 | - name: Install ${{ matrix.msrv }} |
| 165 | uses: dtolnay/rust-toolchain@master | 199 | uses: dtolnay/rust-toolchain@master |
| 166 | with: | 200 | with: |
| 167 | toolchain: ${{ matrix.msrv }} | 201 | toolchain: ${{ matrix.msrv }} |
| 202 | |||
| 168 | - name: cargo +${{ matrix.msrv }} check | 203 | - name: cargo +${{ matrix.msrv }} check |
| 169 | run: cargo check | 204 | run: | |
| 205 | cargo check --all-features --locked | ||
diff --git a/.github/workflows/nostd.yml b/.github/workflows/nostd.yml index 532235851..92460bd0f 100644 --- a/.github/workflows/nostd.yml +++ b/.github/workflows/nostd.yml | |||
| @@ -3,28 +3,41 @@ | |||
| 3 | # information about how the concurrency cancellation and workflow triggering works | 3 | # information about how the concurrency cancellation and workflow triggering works |
| 4 | permissions: | 4 | permissions: |
| 5 | contents: read | 5 | contents: read |
| 6 | |||
| 6 | on: | 7 | on: |
| 7 | push: | 8 | push: |
| 8 | branches: [main] | 9 | branches: [main, main-nextgen] |
| 9 | pull_request: | 10 | pull_request: |
| 11 | |||
| 10 | concurrency: | 12 | concurrency: |
| 11 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} | 13 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} |
| 12 | cancel-in-progress: true | 14 | cancel-in-progress: true |
| 15 | |||
| 13 | name: no-std | 16 | name: no-std |
| 17 | |||
| 14 | jobs: | 18 | jobs: |
| 15 | nostd: | 19 | nostd: |
| 16 | runs-on: ubuntu-latest | 20 | runs-on: ubuntu-latest |
| 17 | name: ${{ matrix.target }} | 21 | name: ${{ matrix.target }} |
| 22 | |||
| 18 | strategy: | 23 | strategy: |
| 19 | matrix: | 24 | matrix: |
| 20 | target: [thumbv8m.main-none-eabihf] | 25 | target: [thumbv8m.main-none-eabihf] |
| 26 | |||
| 21 | steps: | 27 | steps: |
| 22 | - uses: actions/checkout@v4 | 28 | - uses: actions/checkout@v4 |
| 23 | with: | 29 | with: |
| 24 | submodules: true | 30 | submodules: true |
| 31 | |||
| 25 | - name: Install stable | 32 | - name: Install stable |
| 26 | uses: dtolnay/rust-toolchain@stable | 33 | uses: dtolnay/rust-toolchain@stable |
| 34 | |||
| 27 | - name: rustup target add ${{ matrix.target }} | 35 | - name: rustup target add ${{ matrix.target }} |
| 28 | run: rustup target add ${{ matrix.target }} | 36 | run: rustup target add ${{ matrix.target }} |
| 37 | |||
| 38 | - name: Show variable | ||
| 39 | run: echo ${{ env.TOKEN }} | ||
| 40 | |||
| 29 | - name: cargo check | 41 | - name: cargo check |
| 30 | run: cargo check --target ${{ matrix.target }} | 42 | run: | |
| 43 | cargo check --target ${{ matrix.target }} --all-features --locked | ||
diff --git a/.github/workflows/rolling.yml b/.github/workflows/rolling.yml new file mode 100644 index 000000000..f572954f9 --- /dev/null +++ b/.github/workflows/rolling.yml | |||
| @@ -0,0 +1,68 @@ | |||
| 1 | # This workflow runs every morning at midnight. It will run cargo hack | ||
| 2 | # and a build with msrv. If any dependency breaks our crate, we will | ||
| 3 | # know ASAP. | ||
| 4 | # | ||
| 5 | # - check: build with all features | ||
| 6 | # - msrv: check that the msrv specified in the crate is correct | ||
| 7 | permissions: | ||
| 8 | contents: read | ||
| 9 | |||
| 10 | on: | ||
| 11 | schedule: | ||
| 12 | - cron: '0 0 * * *' | ||
| 13 | |||
| 14 | name: rolling | ||
| 15 | jobs: | ||
| 16 | |||
| 17 | check: | ||
| 18 | runs-on: ubuntu-latest | ||
| 19 | name: ubuntu / stable / features | ||
| 20 | strategy: | ||
| 21 | fail-fast: false | ||
| 22 | steps: | ||
| 23 | - uses: actions/checkout@v4 | ||
| 24 | with: | ||
| 25 | submodules: true | ||
| 26 | - name: Install stable | ||
| 27 | uses: dtolnay/rust-toolchain@stable | ||
| 28 | - name: cargo install cargo-hack | ||
| 29 | uses: taiki-e/install-action@cargo-hack | ||
| 30 | - name: cargo check | ||
| 31 | run: | | ||
| 32 | cargo update | ||
| 33 | cargo check --all-features check | ||
| 34 | |||
| 35 | msrv: | ||
| 36 | runs-on: ubuntu-latest | ||
| 37 | strategy: | ||
| 38 | fail-fast: false | ||
| 39 | matrix: | ||
| 40 | msrv: ["1.85"] # We're relying on namespaced-features, which | ||
| 41 | # was released in 1.60 | ||
| 42 | # | ||
| 43 | # We also depend on `fixed' which requires rust | ||
| 44 | # 1.71 | ||
| 45 | # | ||
| 46 | # Additionally, we depend on embedded-hal-async | ||
| 47 | # which requires 1.75 | ||
| 48 | # | ||
| 49 | # embassy-time requires 1.79 due to | ||
| 50 | # collapse_debuginfo | ||
| 51 | # | ||
| 52 | # embassy upstream switched to rust 1.83 | ||
| 53 | # | ||
| 54 | # embedded-services (storage bus) dependency | ||
| 55 | # requires 1.85 | ||
| 56 | name: ubuntu / ${{ matrix.msrv }} (${{ matrix.commit }}) | ||
| 57 | steps: | ||
| 58 | - uses: actions/checkout@v4 | ||
| 59 | with: | ||
| 60 | submodules: true | ||
| 61 | - name: Install ${{ matrix.msrv }} | ||
| 62 | uses: dtolnay/rust-toolchain@master | ||
| 63 | with: | ||
| 64 | toolchain: ${{ matrix.msrv }} | ||
| 65 | - name: cargo +${{ matrix.msrv }} check | ||
| 66 | run: | | ||
| 67 | cargo update | ||
| 68 | cargo check --all-features check | ||
diff --git a/.gitignore b/.gitignore index 6985cf1bd..a23881a76 100644 --- a/.gitignore +++ b/.gitignore | |||
| @@ -1,14 +1,20 @@ | |||
| 1 | # Generated by Cargo | 1 | # Rust |
| 2 | # will have compiled files and executables | 2 | /target/ |
| 3 | debug/ | ||
| 4 | target/ | ||
| 5 | |||
| 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries | ||
| 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html | ||
| 8 | Cargo.lock | 3 | Cargo.lock |
| 9 | 4 | ||
| 10 | # These are backup files generated by rustfmt | 5 | # IDE |
| 11 | **/*.rs.bk | 6 | .vscode/ |
| 7 | .idea/ | ||
| 8 | |||
| 9 | # OS | ||
| 10 | .DS_Store | ||
| 11 | Thumbs.db | ||
| 12 | |||
| 13 | # Embedded | ||
| 14 | *.bin | ||
| 15 | *.hex | ||
| 16 | *.elf | ||
| 17 | *.map | ||
| 12 | 18 | ||
| 13 | # MSVC Windows builds of rustc generate these, which store debugging information | 19 | # Debug |
| 14 | *.pdb | 20 | *.log |
diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index ace00d468..000000000 --- a/.vscode/settings.json +++ /dev/null | |||
| @@ -1,14 +0,0 @@ | |||
| 1 | { | ||
| 2 | "editor.formatOnSave": true, | ||
| 3 | "rust-analyzer.checkOnSave": true, | ||
| 4 | "rust-analyzer.check.allTargets": false, | ||
| 5 | "rust-analyzer.cargo.target": "thumbv8m.main-none-eabihf", | ||
| 6 | "rust-analyzer.cargo.features": "all", | ||
| 7 | "rust-analyzer.check.command": "clippy", | ||
| 8 | "[toml]": { | ||
| 9 | "editor.defaultFormatter": "tamasfe.even-better-toml" | ||
| 10 | }, | ||
| 11 | "[rust]": { | ||
| 12 | "editor.defaultFormatter": "rust-lang.rust-analyzer" | ||
| 13 | } | ||
| 14 | } | ||
diff --git a/CODEOWNERS b/CODEOWNERS deleted file mode 100644 index e2a52ae39..000000000 --- a/CODEOWNERS +++ /dev/null | |||
| @@ -1 +0,0 @@ | |||
| 1 | * @felipebalbi @jerrysxie @tullom @RobertZ2011 @dymk | ||
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 8603671fb..54a673e04 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md | |||
| @@ -129,4 +129,4 @@ For answers to common questions about this code of conduct, see the FAQ at | |||
| 129 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html | 129 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html |
| 130 | [Mozilla CoC]: https://github.com/mozilla/diversity | 130 | [Mozilla CoC]: https://github.com/mozilla/diversity |
| 131 | [FAQ]: https://www.contributor-covenant.org/faq | 131 | [FAQ]: https://www.contributor-covenant.org/faq |
| 132 | [translations]: https://www.contributor-covenant.org/translations \ No newline at end of file | 132 | [translations]: https://www.contributor-covenant.org/translations |
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c45bc8e43..7c8289a58 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md | |||
| @@ -14,22 +14,35 @@ you have the right to submit those contributions under those terms. | |||
| 14 | If you wish to contribute code or documentation authored by others, or using the terms of any other license, please indicate that clearly in your | 14 | If you wish to contribute code or documentation authored by others, or using the terms of any other license, please indicate that clearly in your |
| 15 | pull request so that the project team can discuss the situation with you. | 15 | pull request so that the project team can discuss the situation with you. |
| 16 | 16 | ||
| 17 | ## Commit Message | 17 | # Contribution Guideline |
| 18 | 18 | ||
| 19 | * For any new HAL driver added, please add corresponding test in the examples | ||
| 20 | * Format the code with `cargo fmt`. Or better yet, enable format on save in your IDE for rust source files. | ||
| 19 | * Use meaningful commit messages. See [this blogpost](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) | 21 | * Use meaningful commit messages. See [this blogpost](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) |
| 20 | 22 | ||
| 21 | ## PR Etiquette | 23 | # PR Etiquette |
| 22 | 24 | ||
| 23 | * Create a draft PR first | 25 | * Create a draft PR first |
| 24 | * Make sure that your branch has `.github` folder and all the code linting/sanity check workflows are passing in your draft PR before sending it out to code reviewers. | 26 | * Make sure that your branch has `.github` folder and all the code linting/sanity check workflows are passing in your draft PR before sending it out to code reviewers. |
| 25 | 27 | ||
| 26 | ## Clean Commit History | 28 | # Careful Use of `Unsafe` |
| 27 | 29 | ||
| 28 | We disabled squashing of commit and would like to maintain a clean commit history. So please reorganize your commits with the following items: | 30 | Working with embedded, using of `unsafe` is a necessity. However, please wrap unsafe code with safe interfaces to prevent `unsafe` keyword being sprinkled everywhere. |
| 31 | |||
| 32 | # RFC Draft PR | ||
| 33 | |||
| 34 | If you want feedback on your design or HAL driver early, please create a draft PR with title prefix `RFC:`. | ||
| 35 | |||
| 36 | # Branch Naming Scheme | ||
| 29 | 37 | ||
| 30 | * Each commit builds successfully without warning | 38 | For now, we're not using forks. Eventually a personal fork will be required for any PRs to limit the amount of people with merge access to the main branch. Until that happens, please use meaningful branch names like this `user_alias/feature` and avoid sending PRs from branches containing prefixes such as "wip", "test", etc. Prior to sending a PR, please rename the branch. |
| 31 | * Miscellaneous commits to fix typos + formatting are squashed | 39 | |
| 40 | # Clean Commit History | ||
| 41 | |||
| 42 | We disabled squashing of commit and would like to maintain a clean commit history. So please reorganize your commits with the following items: | ||
| 43 | * Each commit builds successfully without warning from `rustc` or `clippy` | ||
| 44 | * Miscellaneous commits to fix typos + formatting are squashed | ||
| 32 | 45 | ||
| 33 | ## Regressions | 46 | # Regressions |
| 34 | 47 | ||
| 35 | When reporting a regression, please ensure that you use `git bisect` to find the first offending commit, as that will help us finding the culprit a lot faster. | 48 | When reporting a regression, please ensure that you use `git bisect` to find the first offending commit, as that will help us finding the culprit a lot faster. |
diff --git a/Cargo.toml b/Cargo.toml index 71c4806fe..8ec547494 100644 --- a/Cargo.toml +++ b/Cargo.toml | |||
| @@ -1,19 +1,110 @@ | |||
| 1 | [package] | 1 | [package] |
| 2 | name = "embedded-rust-template" | 2 | name = "embassy-mcxa276" |
| 3 | version = "0.1.0" | 3 | version = "0.1.0" |
| 4 | edition = "2021" | 4 | edition = "2021" |
| 5 | license = "MIT" | 5 | license = "MIT OR Apache-2.0" |
| 6 | repository = "https://github.com/OpenDevicePartnership/embedded-rust-template" | 6 | description = "Embassy Hardware Abstraction Layer (HAL) for NXP MCXA276" |
| 7 | rust-version = "1.85" | 7 | keywords = ["embedded", "hal", "nxp", "mcxa276", "embassy"] |
| 8 | categories = ["embedded", "hardware-support", "no-std"] | ||
| 9 | |||
| 10 | [lib] | ||
| 11 | name = "embassy_mcxa276" | ||
| 8 | 12 | ||
| 9 | [dependencies] | 13 | [dependencies] |
| 10 | # dependencies for all targets | 14 | cortex-m = { version = "0.7", features = ["critical-section-single-core"] } |
| 15 | cortex-m-rt = { version = "0.7", features = ["device"] } | ||
| 16 | critical-section = "1.2.0" | ||
| 17 | defmt = { version = "1.0", optional = true } | ||
| 18 | embassy-embedded-hal = "0.5.0" | ||
| 19 | embassy-executor = { version = "0.9.0", features = ["arch-cortex-m", "executor-interrupt", "executor-thread"], default-features = false } | ||
| 20 | embassy-hal-internal = { version = "0.3.0", features = ["cortex-m", "prio-bits-3"] } | ||
| 21 | embassy-sync = "0.7.2" | ||
| 22 | embassy-time = "0.5.0" | ||
| 23 | embassy-time-driver = "0.2.1" | ||
| 24 | embedded-io = "0.6" | ||
| 25 | heapless = "0.8" | ||
| 26 | mcxa-pac = { git = "https://github.com/OpenDevicePartnership/mcxa-pac", features = ["rt"], rev = "f0281344c605ab24c38979553b41a1655c50625c", version = "0.1.0" } | ||
| 27 | paste = "1.0.15" | ||
| 28 | |||
| 29 | embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = [ | ||
| 30 | "unproven", | ||
| 31 | ] } | ||
| 32 | embedded-hal = { package = "embedded-hal", version = "1.0" } | ||
| 33 | embedded-hal-async = { version = "1.0" } | ||
| 34 | embedded-hal-nb = { version = "1.0" } | ||
| 35 | embedded-io-async = { version = "0.6.1" } | ||
| 36 | nb = "1.1.0" | ||
| 37 | |||
| 38 | [dev-dependencies] | ||
| 39 | defmt-rtt = "1.0" | ||
| 40 | panic-probe = { version = "1.0", features = ["print-defmt"] } | ||
| 41 | |||
| 42 | [features] | ||
| 43 | default = [] | ||
| 44 | |||
| 45 | # Base defmt feature enables core + panic handler | ||
| 46 | # Use with one logger feature: defmt-rtt (preferred) or defmt-uart (fallback) | ||
| 47 | defmt = ["dep:defmt"] | ||
| 48 | |||
| 49 | rt = [] | ||
| 50 | |||
| 51 | unstable-pac = [] | ||
| 52 | |||
| 53 | [[example]] | ||
| 54 | name = "hello" | ||
| 55 | required-features = ["lpuart2"] | ||
| 56 | |||
| 57 | [[example]] | ||
| 58 | name = "blink" | ||
| 59 | required-features = ["gpio", "ostimer0"] | ||
| 60 | |||
| 61 | [[example]] | ||
| 62 | name = "uart_interrupt" | ||
| 63 | required-features = ["lpuart2", "ostimer0"] | ||
| 64 | |||
| 65 | [[example]] | ||
| 66 | name = "ostimer_alarm" | ||
| 67 | required-features = ["lpuart2", "ostimer0"] | ||
| 68 | |||
| 69 | [[example]] | ||
| 70 | name = "ostimer_async" | ||
| 71 | required-features = ["lpuart2", "ostimer0"] | ||
| 72 | |||
| 73 | [[example]] | ||
| 74 | name = "ostimer_counter" | ||
| 75 | required-features = ["lpuart2", "ostimer0"] | ||
| 76 | |||
| 77 | [[example]] | ||
| 78 | name = "ostimer_race_test" | ||
| 79 | required-features = ["lpuart2", "ostimer0"] | ||
| 80 | |||
| 81 | [[example]] | ||
| 82 | name = "lpuart_polling" | ||
| 83 | required-features = ["lpuart2", "ostimer0"] | ||
| 84 | |||
| 85 | [[example]] | ||
| 86 | name = "lpuart_buffered" | ||
| 87 | required-features = ["lpuart2", "ostimer0"] | ||
| 88 | |||
| 89 | [[example]] | ||
| 90 | name = "rtc_alarm" | ||
| 91 | required-features = ["lpuart2", "rtc0"] | ||
| 92 | |||
| 93 | [[example]] | ||
| 94 | name = "adc_polling" | ||
| 95 | required-features = ["adc1", "lpuart2"] | ||
| 96 | |||
| 97 | [[example]] | ||
| 98 | name = "adc_interrupt" | ||
| 99 | required-features = ["adc1", "lpuart2"] | ||
| 100 | |||
| 101 | [profile.release] | ||
| 102 | debug = 2 | ||
| 103 | lto = true | ||
| 104 | opt-level = "s" | ||
| 105 | |||
| 106 | [profile.dev] | ||
| 107 | debug = 2 | ||
| 108 | opt-level = 1 | ||
| 11 | 109 | ||
| 12 | [target.'cfg(target_os = "none")'.dependencies] | ||
| 13 | # dependencies for no-std targets | ||
| 14 | 110 | ||
| 15 | [lints.clippy] | ||
| 16 | suspicious = "forbid" | ||
| 17 | correctness = "forbid" | ||
| 18 | perf = "forbid" | ||
| 19 | style = "forbid" | ||
diff --git a/Embed.toml b/Embed.toml new file mode 100644 index 000000000..40218c8bf --- /dev/null +++ b/Embed.toml | |||
| @@ -0,0 +1,28 @@ | |||
| 1 | [default.probe] | ||
| 2 | # Use the first available probe | ||
| 3 | protocol = "Swd" | ||
| 4 | speed = 1000 | ||
| 5 | |||
| 6 | [default.flashing] | ||
| 7 | # Attach-only: don't flash (we already loaded to RAM) | ||
| 8 | enabled = false | ||
| 9 | |||
| 10 | [default.reset] | ||
| 11 | # Don't reset; keep running app started by run_jlink_noblock.sh | ||
| 12 | enabled = false | ||
| 13 | |||
| 14 | [default.general] | ||
| 15 | # The chip name of the target | ||
| 16 | chip = "MCXA276" | ||
| 17 | |||
| 18 | [default.gdb] | ||
| 19 | # Whether or not a GDB server should be opened after loading | ||
| 20 | enabled = false | ||
| 21 | |||
| 22 | [default.rtt] | ||
| 23 | # Enable RTT for debugging output | ||
| 24 | enabled = true | ||
| 25 | |||
| 26 | # Increase timeout for RTT CB discovery | ||
| 27 | up_channels = [ { channel = 0, mode = "BlockIfFull" } ] | ||
| 28 | timeout = 5000 | ||
| @@ -1,6 +1,6 @@ | |||
| 1 | MIT License | 1 | MIT License |
| 2 | 2 | ||
| 3 | Copyright (c) 2025 Open Device Partnership | 3 | Copyright (c) 2025 OEMWCSE |
| 4 | 4 | ||
| 5 | Permission is hereby granted, free of charge, to any person obtaining a copy | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy |
| 6 | of this software and associated documentation files (the "Software"), to deal | 6 | of this software and associated documentation files (the "Software"), to deal |
| @@ -18,4 +18,5 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
| 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| 21 | SOFTWARE. \ No newline at end of file | 21 | SOFTWARE. |
| 22 | |||
| @@ -1,63 +1,362 @@ | |||
| 1 | # embedded-rust-template | 1 | # Embassy MCXA276 HAL |
| 2 | Template repository for Embedded Rust development | ||
| 3 | 2 | ||
| 4 | ## Customizing This Template | 3 | A Hardware Abstraction Layer (HAL) for the NXP MCXA276 microcontroller |
| 4 | using the Embassy async framework. This HAL provides safe, idiomatic | ||
| 5 | Rust interfaces for GPIO, UART, and OSTIMER peripherals. | ||
| 5 | 6 | ||
| 6 | ### Changing the Target Architecture | 7 | ## Prerequisites |
| 7 | 8 | ||
| 8 | This template is configured for `thumbv8m.main-none-eabihf`, by default, but you can modify it for other targets (i.e. `aarch64-unknown-none`): | 9 | ### Ubuntu/Debian Setup |
| 9 | 10 | ||
| 10 | 1. **VSCode Settings**: Update the target in `.vscode/settings.json`: | 11 | ```bash |
| 11 | ```json | 12 | # Install Rust toolchain |
| 12 | "rust-analyzer.cargo.target": "your-target-architecture" | 13 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh |
| 13 | ``` | 14 | source ~/.cargo/env |
| 14 | 15 | ||
| 16 | # Add target for MCXA276 (ARM Cortex-M33) | ||
| 17 | rustup target add thumbv8m.main-none-eabihf | ||
| 15 | 18 | ||
| 16 | This configuration ensures that: | 19 | # Install required tools |
| 17 | - Only the specified target architecture is analyzed, not the host platform | 20 | sudo apt update |
| 18 | - Code is checked against the no_std environment | 21 | sudo apt install -y gdb-multiarch curl wget |
| 19 | 22 | ||
| 20 | To temporarily analyze code for the host platform instead, you can remove the `rust-analyzer.cargo.target` setting. | 23 | # Install probe-rs for running and debugging |
| 24 | cargo install probe-rs --features cli | ||
| 25 | ``` | ||
| 26 | |||
| 27 | ### Windows Setup | ||
| 28 | |||
| 29 | - Install Rust via https://rustup.rs (default options are fine) | ||
| 30 | - Add the MCXA276 target: | ||
| 31 | ```powershell | ||
| 32 | rustup target add thumbv8m.main-none-eabihf | ||
| 33 | ``` | ||
| 34 | - Install probe-rs CLI (we will use it directly; no GDB required): | ||
| 35 | ```powershell | ||
| 36 | cargo install probe-rs --features cli | ||
| 37 | ``` | ||
| 38 | - Install a serial terminal (e.g., Tera Term): https://ttssh2.osdn.jp/ | ||
| 39 | - USB drivers: Windows 10/11 usually picks up the board as a USB CDC device automatically (COM port) | ||
| 40 | |||
| 41 | ### Hardware Requirements | ||
| 42 | |||
| 43 | - NXP FRDM-MCXA276 development board | ||
| 44 | - Debug probe (CMSIS-DAP compatible) | ||
| 45 | - USB cable for power and programming | ||
| 46 | |||
| 47 | ## Examples | ||
| 48 | |||
| 49 | This HAL includes several examples demonstrating different peripherals: | ||
| 50 | |||
| 51 | ### GPIO Examples | ||
| 52 | |||
| 53 | #### `blink` | ||
| 54 | Blinks an LED connected to GPIO pin. Demonstrates basic GPIO output operations. | ||
| 55 | |||
| 56 | ### UART Examples | ||
| 57 | |||
| 58 | #### `hello` | ||
| 59 | Interactive UART2 demo: prints a banner and supports `help`, `echo <text>`, `hex <byte>`. | ||
| 60 | |||
| 61 | ### OSTIMER Examples | ||
| 62 | |||
| 63 | #### `ostimer_alarm` | ||
| 64 | |||
| 65 | Demonstrates setting and waiting for OSTIMER alarms. | ||
| 66 | |||
| 67 | #### `ostimer_async` | ||
| 68 | Shows asynchronous OSTIMER operations with Embassy's async runtime. | ||
| 69 | |||
| 70 | #### `ostimer_counter` | ||
| 71 | Demonstrates OSTIMER counter functionality. | ||
| 72 | |||
| 73 | #### `ostimer_race_test` | ||
| 74 | Advanced example testing OSTIMER race conditions and synchronization. | ||
| 75 | |||
| 76 | ### RTC Example | ||
| 77 | |||
| 78 | #### `rtc_alarm` | ||
| 79 | Demonstrates how to enable and use the RTC to generate an interrupt after 10seconds. | ||
| 80 | |||
| 81 | ## Build and Run | ||
| 82 | |||
| 83 | ### Using probe-rs | ||
| 84 | |||
| 85 | All examples require specifying your debug probe. First, find your probe ID: | ||
| 21 | 86 | ||
| 22 | 2. **GitHub Workflows**: Modify the target in two workflow files: | 87 | ```bash |
| 23 | - `.github/workflows/nostd.yml`: Update the targets in the matrix: | 88 | probe-rs list |
| 24 | ```yaml | 89 | ``` |
| 25 | matrix: | 90 | |
| 26 | target: [your-target-architecture] | 91 | Then run examples with your probe ID (replace `1fc9:0143:H3AYDQVQMTROB` with your actual probe): |
| 27 | ``` | 92 | |
| 28 | - `.github/workflows/check.yml`: If there are any target-specific checks, update them accordingly. | 93 | ```bash |
| 94 | # GPIO blink example | ||
| 95 | PROBE=1fc9:0143:H3AYDQVQMTROB cargo run --features "gpio ostimer0" --example blink | ||
| 96 | |||
| 97 | |||
| 98 | |||
| 99 | # UART hello example | ||
| 100 | PROBE=1fc9:0143:H3AYDQVQMTROB cargo run --features "lpuart2 ostimer0" --example hello | ||
| 101 | |||
| 102 | # OSTIMER examples | ||
| 103 | PROBE=1fc9:0143:H3AYDQVQMTROB cargo run --features "lpuart2 ostimer0" --example ostimer_alarm | ||
| 104 | PROBE=1fc9:0143:H3AYDQVQMTROB cargo run --features "lpuart2 ostimer0" --example ostimer_async | ||
| 105 | PROBE=1fc9:0143:H3AYDQVQMTROB cargo run --features "lpuart2 ostimer0" --example ostimer_counter | ||
| 106 | PROBE=1fc9:0143:H3AYDQVQMTROB cargo run --features "lpuart2 ostimer0" --example ostimer_race_test | ||
| 107 | |||
| 108 | # RTC example | ||
| 109 | PROBE=1fc9:0143:H3AYDQVQMTROB cargo run --features "lpuart2 rtc0" --example rtc_alarm | ||
| 110 | ``` | ||
| 111 | |||
| 112 | **Note:** All examples run from RAM, not flash memory. They are loaded directly into RAM for faster development iteration. | ||
| 113 | |||
| 114 | **Important:** After pressing the RESET button on the board, the first `cargo run` attempt may fail with a connection error. This is expected - simply run the command again and it will work. The run.sh script now properly sets the Vector Table Offset Register (VTOR) to point to the RAM-based vector table, ensuring the correct stack pointer and reset vector are used. | ||
| 29 | 115 | ||
| 30 | 3. **Cargo Configuration**: If needed, you can add target-specific configuration in a `.cargo/config.toml` file. | 116 | ```console |
| 117 | smw016108@smw016108:~/Downloads/nxp/rust/uart/embassy-mcxa276$ PROBE=1fc9:0143:H3AYDQVQMTROB cargo run --release --features "gpio ostimer0" --example blink | ||
| 118 | Finished `release` profile [optimized + debuginfo] target(s) in 0.07s | ||
| 119 | Running `/home/smw016108/Downloads/nxp/rust/uart/embassy-mcxa276/./run.sh target/thumbv8m.main-none-eabihf/release/examples/blink` | ||
| 120 | probe-rs gdb server failed to connect to target. Log: | ||
| 121 | ----- probe-rs gdb log ----- | ||
| 122 | Error: Connecting to the chip was unsuccessful. | ||
| 31 | 123 | ||
| 32 | ### Converting from Binary to Library | 124 | Caused by: |
| 125 | 0: An ARM specific error occurred. | ||
| 126 | 1: Error using access port FullyQualifiedApAddress { dp: Default, ap: V1(0) }. | ||
| 127 | 2: Failed to read register DRW at address 0xd0c | ||
| 128 | 3: An error occurred in the communication with an access port or debug port. | ||
| 129 | 4: Target device responded with a FAULT response to the request. | ||
| 130 | smw016108@smw016108:~/Downloads/nxp/rust/uart/embassy-mcxa276$ PROBE=1fc9:0143:H3AYDQVQMTROB cargo run --release --features "gpio ostimer0" --example blink | ||
| 131 | Finished `release` profile [optimized + debuginfo] target(s) in 0.02s | ||
| 132 | Running `/home/smw016108/Downloads/nxp/rust/uart/embassy-mcxa276/./run.sh target/thumbv8m.main-none-eabihf/release/examples/blink` | ||
| 133 | ``` | ||
| 134 | |||
| 135 | ### Additional UART Examples | ||
| 136 | |||
| 137 | #### `uart_interrupt` | ||
| 138 | Interrupt-driven UART2 echo. Type in the serial terminal; each byte is echoed back from the IRQ handler path. | ||
| 33 | 139 | ||
| 34 | To convert this project from a binary to a library: | 140 | #### `lpuart_polling` |
| 141 | Blocking TX/RX echo over UART2 using the simple polling driver. | ||
| 35 | 142 | ||
| 36 | 1. **Cargo.toml**: Update your project structure: | 143 | #### `lpuart_buffered` |
| 37 | ```toml | 144 | Async buffered driver with separate TX/RX tasks; echoes typed characters in chunks. |
| 38 | [lib] | ||
| 39 | name = "your_library_name" | ||
| 40 | ``` | ||
| 41 | 145 | ||
| 42 | 2. **Directory Structure**: | 146 | Pins: UART2 TX=P2_2, RX=P2_3 (ALT3), 115200 8N1. |
| 43 | - For a library, ensure you have a `src/lib.rs` file instead of `src/main.rs` | ||
| 44 | - Move your code from `main.rs` to `lib.rs` and adjust as needed | ||
| 45 | 147 | ||
| 46 | 3. **No-std Configuration**: If you're creating a no-std library, ensure you have: | 148 | ### ADC Examples |
| 47 | ```rust | ||
| 48 | // In lib.rs | ||
| 49 | #![cfg_attr(target_os = "none", no_std)] | ||
| 50 | // Add other attributes as needed | ||
| 51 | ``` | ||
| 52 | 149 | ||
| 53 | ### Project Dependencies | 150 | #### `adc_polling` |
| 151 | Configures ADC1 channel A8 (pin P1_10) and prints conversion values to UART2 periodically. | ||
| 54 | 152 | ||
| 55 | Update the dependencies in `Cargo.toml` based on your target platform: | 153 | #### `adc_interrupt` |
| 154 | Triggers a conversion and signals completion via ADC1 interrupt, printing a notification on UART2. | ||
| 56 | 155 | ||
| 57 | ```toml | 156 | ```console |
| 58 | [dependencies] | 157 | 0x20002040 in ?? () |
| 59 | # Common dependencies for all targets | 158 | Supported Commands: |
| 60 | 159 | ||
| 61 | [target.'cfg(target_os = "none")'.dependencies] | 160 | info - print session information |
| 62 | # Dependencies for no-std targets | 161 | reset - reset target |
| 162 | reset halt - reset target and halt afterwards | ||
| 163 | |||
| 164 | Loading section .vector_table, size 0x224 lma 0x20000000 | ||
| 165 | Loading section .text, size 0x97e lma 0x20000224 | ||
| 166 | Loading section .Reset, size 0x58 lma 0x20000ba4 | ||
| 167 | Loading section .rodata, size 0x28 lma 0x20000bfc | ||
| 168 | Start address 0x20000ba4, load size 3106 | ||
| 169 | Transfer rate: 13 KB/sec, 776 bytes/write. | ||
| 63 | ``` | 170 | ``` |
| 171 | |||
| 172 | then I see the LED blinking. I press CTRL+C to exit. It will show me ^C | ||
| 173 | |||
| 174 | ```console | ||
| 175 | Program received signal SIGINT, Interrupt. | ||
| 176 | 0x20000880 in embassy_executor::arch::thread::Executor::run<blink::__cortex_m_rt_main::{closure_env#0}> (self=0x200027e8, init=...) at /home/smw016108/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/embassy-executor-0.9.1/src/arch/cortex_m.rs:106 | ||
| 177 | 106 asm!("wfe"); | ||
| 178 | [Inferior 1 (process 1) detached] | ||
| 179 | Program loaded and started (no reset) | ||
| 180 | smw016108@smw016108:~/Downloads/nxp/rust/uart/embassy-mcxa276$ \ | ||
| 181 | |||
| 182 | Then I press RESET again and I want to run another example, like ostimer_alarm. I open the console using sudo picocom -b 115200 /dev/ttyACM0 and I start running the example: | ||
| 183 | |||
| 184 | smw016108@smw016108:~/Downloads/nxp/rust/uart/embassy-mcxa276$ PROBE=1fc9:0143:H3AYDQVQMTROB cargo run --features "lpuart2 ostimer0" --example ostimer_alarm | ||
| 185 | Finished `dev` profile [optimized + debuginfo] target(s) in 0.02s | ||
| 186 | Running `/home/smw016108/Downloads/nxp/rust/uart/embassy-mcxa276/./run.sh target/thumbv8m.main-none-eabihf/debug/examples/ostimer_alarm` | ||
| 187 | probe-rs gdb server failed to connect to target. Log: | ||
| 188 | ----- probe-rs gdb log ----- | ||
| 189 | Error: Connecting to the chip was unsuccessful. | ||
| 190 | |||
| 191 | Caused by: | ||
| 192 | 0: An ARM specific error occurred. | ||
| 193 | 1: Error using access port FullyQualifiedApAddress { dp: Default, ap: V1(0) }. | ||
| 194 | 2: Failed to read register DRW at address 0xd0c | ||
| 195 | 3: An error occurred in the communication with an access port or debug port. | ||
| 196 | 4: Target device responded with a FAULT response to the request. | ||
| 197 | smw016108@smw016108:~/Downloads/nxp/rust/uart/embassy-mcxa276$ PROBE=1fc9:0143:H3AYDQVQMTROB cargo run --features "lpuart2 ostimer0" --example ostimer_alarm | ||
| 198 | Finished `dev` profile [optimized + debuginfo] target(s) in 0.02s | ||
| 199 | Running `/home/smw016108/Downloads/nxp/rust/uart/embassy-mcxa276/./run.sh target/thumbv8m.main-none-eabihf/debug/examples/ostimer_alarm` | ||
| 200 | 0x20002040 in core::panicking::panic_const::panic_const_mul_overflow () at library/core/src/panicking.rs:175 | ||
| 201 | warning: 175 library/core/src/panicking.rs: No such file or directory | ||
| 202 | Supported Commands: | ||
| 203 | |||
| 204 | info - print session information | ||
| 205 | reset - reset target | ||
| 206 | reset halt - reset target and halt afterwards | ||
| 207 | |||
| 208 | Loading section .vector_table, size 0x224 lma 0x20000000 | ||
| 209 | Loading section .text, size 0x2226 lma 0x20000224 | ||
| 210 | Loading section .Reset, size 0x58 lma 0x2000244c | ||
| 211 | Loading section .rodata, size 0x6dc lma 0x200024a4 | ||
| 212 | Start address 0x2000244c, load size 11134 | ||
| 213 | Transfer rate: 16 KB/sec, 1855 bytes/write. | ||
| 214 | ``` | ||
| 215 | |||
| 216 | I can see in the console | ||
| 217 | |||
| 218 | ```console | ||
| 219 | OSTIMER Alarm Example | ||
| 220 | Scheduling alarm for 2 seconds... | ||
| 221 | Alarm scheduled successfully | ||
| 222 | Alarm expired! Callback executed. | ||
| 223 | Scheduling another alarm for 3 seconds... | ||
| 224 | Alarm scheduled. Waiting 1 second then canceling... | ||
| 225 | Alarm canceled | ||
| 226 | Alarm was successfully canceled | ||
| 227 | Example complete | ||
| 228 | ``` | ||
| 229 | |||
| 230 | then I press CTRL+C to stop running | ||
| 231 | |||
| 232 | ```console | ||
| 233 | ^C | ||
| 234 | Program received signal SIGINT, Interrupt. | ||
| 235 | 0x20000e64 in embassy_executor::arch::thread::Executor::run<ostimer_alarm::__cortex_m_rt_main::{closure_env#0}> (self=0x200027e8, init=...) at /home/smw016108/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/embassy-executor-0.9.1/src/arch/cortex_m.rs:106 | ||
| 236 | 106 asm!("wfe"); | ||
| 237 | [Inferior 1 (process 1) detached] | ||
| 238 | Program loaded and started (no reset) | ||
| 239 | smw016108@smw016108:~/Downloads/nxp/rust/uart/embassy-mcxa276$ | ||
| 240 | ``` | ||
| 241 | |||
| 242 | ### Windows: Running examples (RAM, no RTT/defmt) | ||
| 243 | |||
| 244 | Important: On Windows, do not use `cargo run` because `.cargo/config.toml` sets a Linux-only runner (`./run.sh`). Instead, use `probe-rs run` directly. | ||
| 245 | |||
| 246 | 1) Find your probe and COM port | ||
| 247 | - List probes: | ||
| 248 | |||
| 249 | ```console | ||
| 250 | probe-rs list | ||
| 251 | ``` | ||
| 252 | - If multiple probes are attached, set the specific one (replace with your ID): | ||
| 253 | |||
| 254 | ```console | ||
| 255 | $env:PROBE_RS_PROBE = "1366:0101:000600110607" | ||
| 256 | ``` | ||
| 257 | |||
| 258 | - Check Windows Device Manager → Ports (COM & LPT) for the board’s COM port. | ||
| 259 | |||
| 260 | 2) Build the example | ||
| 261 | |||
| 262 | ```console | ||
| 263 | cargo build --example hello --features "lpuart2" | ||
| 264 | ``` | ||
| 265 | |||
| 266 | 3) Run from RAM with probe-rs | ||
| 267 | |||
| 268 | ```console | ||
| 269 | probe-rs run --chip MCXA276 --protocol swd --speed 1000 target/thumbv8m.main-none-eabihf/debug/examples/hello | ||
| 270 | ``` | ||
| 271 | You will see a short probe-rs warning like "unknown variant, try to set watch point"; it’s harmless. | ||
| 272 | |||
| 273 | 4) View output in Tera Term | ||
| 274 | - Open Tera Term, select the board’s COMx port, 115200 8N1 | ||
| 275 | - Expected behavior per example: | ||
| 276 | - hello: prints a banner; simple UART output | ||
| 277 | - lpuart_polling / lpuart_buffered / uart_interrupt: echo typed characters | ||
| 278 | - adc_polling: prints ADC values periodically (ADC1 channel A8 on P1_10) | ||
| 279 | - adc_interrupt: prints "*** ADC interrupt TRIGGERED! ***" upon conversion completion | ||
| 280 | - blink: LED on PIO3_18 blinks "SOS" pattern | ||
| 281 | - rtc_alarm: schedules, cancels and reports alarm events on UART | ||
| 282 | |||
| 283 | Notes | ||
| 284 | - All examples run from RAM (not flashed). Reset clears the program. | ||
| 285 | - If the first attempt after a reset fails to connect, just run the command again. | ||
| 286 | - UART2 pins: TX=P2_2, RX=P2_3 (ALT3), 115200 8N1. | ||
| 287 | |||
| 288 | Quick commands for other examples: | ||
| 289 | |||
| 290 | ```console | ||
| 291 | # Build | ||
| 292 | cargo build --example blink --features "gpio ostimer0" | ||
| 293 | cargo build --example lpuart_polling --features "lpuart2 ostimer0" | ||
| 294 | cargo build --example lpuart_buffered --features "lpuart2 ostimer0" | ||
| 295 | cargo build --example uart_interrupt --features "lpuart2 ostimer0" | ||
| 296 | cargo build --example rtc_alarm --features "lpuart2 rtc0" | ||
| 297 | cargo build --example adc_polling --features "adc1 lpuart2" | ||
| 298 | cargo build --example adc_interrupt --features "adc1 lpuart2" | ||
| 299 | |||
| 300 | # Run (RAM) | ||
| 301 | probe-rs run --chip MCXA276 --protocol swd --speed 1000 target/thumbv8m.main-none-eabihf/debug/examples/blink | ||
| 302 | probe-rs run --chip MCXA276 --protocol swd --speed 1000 target/thumbv8m.main-none-eabihf/debug/examples/lpuart_polling | ||
| 303 | probe-rs run --chip MCXA276 --protocol swd --speed 1000 target/thumbv8m.main-none-eabihf/debug/examples/lpuart_buffered | ||
| 304 | probe-rs run --chip MCXA276 --protocol swd --speed 1000 target/thumbv8m.main-none-eabihf/debug/examples/uart_interrupt | ||
| 305 | probe-rs run --chip MCXA276 --protocol swd --speed 1000 target/thumbv8m.main-none-eabihf/debug/examples/rtc_alarm | ||
| 306 | probe-rs run --chip MCXA276 --protocol swd --speed 1000 target/thumbv8m.main-none-eabihf/debug/examples/adc_polling | ||
| 307 | probe-rs run --chip MCXA276 --protocol swd --speed 1000 target/thumbv8m.main-none-eabihf/debug/examples/adc_interrupt | ||
| 308 | probe-rs run --chip MCXA276 --protocol swd --speed 1000 target/thumbv8m.main-none-eabihf/debug/examples/ostimer_alarm | ||
| 309 | probe-rs run --chip MCXA276 --protocol swd --speed 1000 target/thumbv8m.main-none-eabihf/debug/examples/ostimer_async | ||
| 310 | probe-rs run --chip MCXA276 --protocol swd --speed 1000 target/thumbv8m.main-none-eabihf/debug/examples/ostimer_counter | ||
| 311 | probe-rs run --chip MCXA276 --protocol swd --speed 1000 target/thumbv8m.main-none-eabihf/debug/examples/ostimer_race_test | ||
| 312 | ``` | ||
| 313 | |||
| 314 | How I tested on Windows | ||
| 315 | - Windows 11; Rust stable; probe-rs 0.29.x | ||
| 316 | - Built each example as above; ran with `probe-rs run` (RAM execution) | ||
| 317 | - Observed UART output in Tera Term at 115200 8N1; all examples behaved as expected | ||
| 318 | - No RTT/defmt used; purely UART or LED observation | ||
| 319 | |||
| 320 | ### Build Only | ||
| 321 | |||
| 322 | To build without running: | ||
| 323 | |||
| 324 | ```console | ||
| 325 | cargo build --features "gpio ostimer0" --example blink | ||
| 326 | cargo build --features "lpuart2 ostimer0" --example hello | ||
| 327 | cargo build --features "lpuart2 ostimer0" --example ostimer_alarm | ||
| 328 | cargo build --features "lpuart2 rtc0" --example rtc_alarm | ||
| 329 | # etc. | ||
| 330 | ``` | ||
| 331 | |||
| 332 | ## Development Notes | ||
| 333 | |||
| 334 | ### Critical Fix: MCXA276 Interrupt Vector Table | ||
| 335 | |||
| 336 | |||
| 337 | Update (SVD 25.06.00, mcxa-pac a9dd33): No manual PAC edits are required anymore. OS_EVENT and WAKETIMER0 are present and the vector table is correct. The section below is kept for historical context. | ||
| 338 | **Problem:** The OSTIMER examples crashed during interrupt handling with a hardfault (SP=0x00000000). Investigation revealed the OS_EVENT interrupt vector was NULL in the vector table, causing the CPU to jump to address 0 when OSTIMER interrupts fired. | ||
| 339 | |||
| 340 | **Root Cause:** The `mcxa276-pac/src/lib.rs` file (generated from the SVD file) was missing the `WAKETIMER0` interrupt handler declaration. This caused the `__INTERRUPTS` array to have an off-by-one error, placing OS_EVENT at IRQ 58 instead of the correct IRQ 57 position. | ||
| 341 | |||
| 342 | **Solution:** Manually edited `mcxa276-pac/src/lib.rs` to add the missing WAKETIMER0 interrupt: | ||
| 343 | |||
| 344 | 1. Added `fn WAKETIMER0()` to the `extern "C"` block | ||
| 345 | 2. Fixed the `__INTERRUPTS: [Vector; 122]` array sequence: | ||
| 346 | - Changed from: `LPTMR0, _reserved, _reserved, OS_EVENT, _reserved, UTICK0, ...` | ||
| 347 | - Changed to: `LPTMR0, _reserved, OS_EVENT, WAKETIMER0, UTICK0, WWDT0, _reserved, ADC0, ...` | ||
| 348 | 3. Added `WAKETIMER0 = 58` to the `Interrupt` enum | ||
| 349 | |||
| 350 | **Verification:** Binary analysis confirmed OS_EVENT is now at the correct position: | ||
| 351 | - IRQ 57 = word 73 = offset 0x124 in vector table | ||
| 352 | - OS_EVENT handler: 0x20000BB1 (verified with `arm-none-eabi-objdump`) | ||
| 353 | |||
| 354 | **Note:** This is likely an issue with the NXP MCXA276.svd file or svd2rust generation. The WAKETIMER0 peripheral exists in the PAC but the interrupt handler was missing. Future regeneration of the PAC from SVD may require reapplying this fix. | ||
| 355 | |||
| 356 | ### Warning: Avoid `#[inline(always)]` in Performance-Critical Code | ||
| 357 | |||
| 358 | Using `#[inline(always)]` can cause the Rust compiler to generate incorrect assembly, leading to register corruption or unexpected behavior. For example, in tight polling loops like those in the OSTIMER driver, this attribute may result in invalid instructions that zero registers (e.g., `movs r1, r0` causing r1=0), triggering hardfaults. | ||
| 359 | |||
| 360 | ## License | ||
| 361 | |||
| 362 | This project is licensed under MIT OR Apache-2.0. | ||
diff --git a/SECURITY.md b/SECURITY.md index 6ed358156..5357b8824 100644 --- a/SECURITY.md +++ b/SECURITY.md | |||
| @@ -63,4 +63,4 @@ An embargo can be called for in various cases: | |||
| 63 | If we determine that an issue you report requires an embargo, we will discuss | 63 | If we determine that an issue you report requires an embargo, we will discuss |
| 64 | this with you and try to find a reasonable expiry date (aka “embargo | 64 | this with you and try to find a reasonable expiry date (aka “embargo |
| 65 | completion date”), as well as who should be included in the list of | 65 | completion date”), as well as who should be included in the list of |
| 66 | need-to-know people. \ No newline at end of file | 66 | need-to-know people. |
diff --git a/SW-Content-Register.txt b/SW-Content-Register.txt new file mode 100644 index 000000000..09f879c2f --- /dev/null +++ b/SW-Content-Register.txt | |||
| @@ -0,0 +1,78 @@ | |||
| 1 | Release Name: Embassy MCXA276 HAL | ||
| 2 | Release Version: 0.1.0 | ||
| 3 | Package License: MIT (see ./License.txt) | ||
| 4 | Note: The crate is dual-licensed “MIT OR Apache-2.0” per Cargo.toml; choosing MIT satisfies the dual-license terms. | ||
| 5 | |||
| 6 | Scope of this Register | ||
| 7 | This file documents software content that is part of this repository and, for transparency, lists major non-vendored Rust dependencies that are resolved from crates.io during builds. Components that are not present in the repository (e.g., external crates) are listed under “Non‑vendored Rust dependencies”. | ||
| 8 | |||
| 9 | Repository Components (included in this release) | ||
| 10 | |||
| 11 | embassy_mcxa276_hal Name: Embassy MCXA276 HAL (library + examples) | ||
| 12 | Version: 0.1.0 | ||
| 13 | Outgoing License: MIT | ||
| 14 | License File: ./License.txt | ||
| 15 | Format: Rust source code, examples, scripts, linker scripts | ||
| 16 | Description: Hardware Abstraction Layer for NXP MCXA276 using the Embassy async framework. Implements drivers and helpers for: | ||
| 17 | - LPUART2 (UART), OSTIMER0, RTC0, ADC1, GPIO | ||
| 18 | - Embassy integration (executors, time) | ||
| 19 | Location: ./src, ./examples, ./build.rs, ./memory.x, ./ram.ld, ./defmt.x, ./.cargo/config.toml, ./run.sh, ./tools/ | ||
| 20 | Origin: OEMWCSE (MIT) | ||
| 21 | URL: https://bitbucket.sw.nxp.com/scm/oemwcse/mcxa_rust.git | ||
| 22 | |||
| 23 | mcxa276_pac Name: MCXA276 Peripheral Access Crate (PAC) | ||
| 24 | Version: 0.1.0 | ||
| 25 | Outgoing License: MIT OR Apache-2.0 | ||
| 26 | License File: (see crate metadata) | ||
| 27 | Format: Rust source code (auto-generated PAC) | ||
| 28 | Description: Auto-generated register mappings for MCXA276 peripherals (svd2rust). Includes device.x and interrupt vectors. | ||
| 29 | Location: External (git): https://github.com/bogdan-petru/mcxa-pac (pinned rev a9dd3301) | ||
| 30 | Origin: Generated by svdtools + svd2rust from NXP SVD 25.06.00 | ||
| 31 | URL: N/A (generated from NXP SVD) | ||
| 32 | |||
| 33 | examples Name: Example applications | ||
| 34 | Version: N/A | ||
| 35 | Outgoing License: MIT | ||
| 36 | License File: ./License.txt | ||
| 37 | Format: Rust source code (examples) | ||
| 38 | Description: Demonstrations for HAL peripherals and features: | ||
| 39 | - hello, blink | ||
| 40 | - lpuart_polling, lpuart_buffered, uart_interrupt | ||
| 41 | - rtc_alarm | ||
| 42 | - adc_polling, adc_interrupt | ||
| 43 | - ostimer_alarm, ostimer_async, ostimer_counter, ostimer_race_test | ||
| 44 | Location: ./examples | ||
| 45 | Origin: OEMWCSE (MIT) | ||
| 46 | |||
| 47 | Non‑vendored Rust dependencies (resolved from crates.io at build time) | ||
| 48 | The following crates are not vendored in this repository. They are fetched during builds via Cargo. See Cargo.lock for exact versions. License summaries below are for convenience; consult each crate for authoritative terms. | ||
| 49 | |||
| 50 | cortex-m License: MIT OR Apache-2.0 Purpose: Cortex-M core support | ||
| 51 | cortex-m-rt License: MIT OR Apache-2.0 Purpose: Cortex-M runtime (vectors, reset) | ||
| 52 | embassy-executor License: MIT OR Apache-2.0 Purpose: Async executor for Cortex-M | ||
| 53 | embassy-time (+ driver) License: MIT OR Apache-2.0 Purpose: Time abstraction and drivers | ||
| 54 | embassy-sync License: MIT OR Apache-2.0 Purpose: No-std synchronization primitives | ||
| 55 | embassy-embedded-hal License: MIT OR Apache-2.0 Purpose: Traits/adapters for embedded-hal | ||
| 56 | embassy-hal-internal License: MIT OR Apache-2.0 Purpose: HAL building blocks (peripherals!) | ||
| 57 | embedded-hal (1.0) License: MIT OR Apache-2.0 Purpose: Core embedded hardware traits | ||
| 58 | embedded-hal (0.2.6) License: MIT OR Apache-2.0 Purpose: Legacy HAL traits ("unproven") | ||
| 59 | embedded-hal-async License: MIT OR Apache-2.0 Purpose: Async HAL traits | ||
| 60 | embedded-hal-nb License: MIT OR Apache-2.0 Purpose: Non-blocking HAL traits | ||
| 61 | embedded-io / embedded-io-async License: MIT OR Apache-2.0 Purpose: IO traits | ||
| 62 | critical-section License: MIT OR Apache-2.0 Purpose: Critical-section abstraction | ||
| 63 | heapless License: MIT OR Apache-2.0 Purpose: Fixed-capacity data structures | ||
| 64 | paste License: MIT OR Apache-2.0 Purpose: Macro hygiene utility | ||
| 65 | nb License: MIT OR Apache-2.0 Purpose: Non-blocking result type | ||
| 66 | vcell License: MIT OR Apache-2.0 Purpose: Volatile cell (PAC dependency) | ||
| 67 | |||
| 68 | defmt, defmt-rtt, rtt-target, panic-probe License: MIT OR Apache-2.0 Purpose: Optional (feature-gated) logging/panic crates | ||
| 69 | |||
| 70 | Attribution notes | ||
| 71 | - MCXA276 PAC is sourced as an external dependency (see Cargo.toml). SVD 25.06.00 already includes OS_EVENT and WAKETIMER0 interrupts (no local fixes). | ||
| 72 | - Examples run from RAM using custom linker setup; see README.txt for platform-specific instructions. | ||
| 73 | |||
| 74 | Compliance | ||
| 75 | - This repository’s distributed source is covered by MIT (see License.txt). Where crates are pulled from crates.io, those crates retain their own licenses; the dual-license compatibility (MIT OR Apache-2.0) is standard in the Rust embedded ecosystem and is compatible with this project’s selected MIT distribution. | ||
| 76 | |||
| 77 | |||
| 78 | |||
diff --git a/build.rs b/build.rs new file mode 100644 index 000000000..645843590 --- /dev/null +++ b/build.rs | |||
| @@ -0,0 +1,20 @@ | |||
| 1 | use std::env; | ||
| 2 | use std::fs::File; | ||
| 3 | use std::io::Write; | ||
| 4 | use std::path::PathBuf; | ||
| 5 | |||
| 6 | fn main() { | ||
| 7 | let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); | ||
| 8 | |||
| 9 | // Generate memory.x - put "FLASH" at start of RAM, RAM after "FLASH" | ||
| 10 | // cortex-m-rt expects FLASH for code, RAM for data/bss/stack | ||
| 11 | // Both are in RAM, but separated to satisfy cortex-m-rt's expectations | ||
| 12 | // MCXA276 has 128KB RAM total | ||
| 13 | File::create(out.join("memory.x")) | ||
| 14 | .unwrap() | ||
| 15 | .write_all(b"/* MCXA276 RAM-execution: FLASH region holds code, RAM region for data/stack */\nMEMORY { FLASH : ORIGIN = 0x20000000, LENGTH = 64K\n RAM : ORIGIN = 0x20010000, LENGTH = 64K }\n") | ||
| 16 | .unwrap(); | ||
| 17 | |||
| 18 | println!("cargo:rustc-link-search={}", out.display()); | ||
| 19 | println!("cargo:rerun-if-changed=memory.x"); | ||
| 20 | } | ||
diff --git a/defmt.x b/defmt.x new file mode 100644 index 000000000..dbd6d0850 --- /dev/null +++ b/defmt.x | |||
| @@ -0,0 +1,6 @@ | |||
| 1 | /* | ||
| 2 | Dummy defmt.x to satisfy -Tdefmt.x when building without the `defmt` feature. | ||
| 3 | When `defmt` is enabled, the real defmt.x provided by the defmt crate is used via the linker search path. | ||
| 4 | This file intentionally contains no SECTIONS so it won't override ram.ld. | ||
| 5 | */ | ||
| 6 | |||
| @@ -74,8 +74,9 @@ ignore = [ | |||
| 74 | #{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" }, | 74 | #{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" }, |
| 75 | #"[email protected]", # you can also ignore yanked crate versions if you wish | 75 | #"[email protected]", # you can also ignore yanked crate versions if you wish |
| 76 | #{ crate = "[email protected]", reason = "you can specify why you are ignoring the yanked crate" }, | 76 | #{ crate = "[email protected]", reason = "you can specify why you are ignoring the yanked crate" }, |
| 77 | { id = "RUSTSEC-2024-0370", reason = "proc-macro-error is unmaintained, no safe upgrade available, need upstream dependencies to migrate away from it." }, | 77 | # { id = "RUSTSEC-2024-0370", reason = "proc-macro-error is unmaintained, no safe upgrade available, need upstream dependencies to migrate away from it." }, |
| 78 | { id = "RUSTSEC-2024-0436", reason = "there are no suitable replacements for paste right now; paste has been archived as read-only. It only affects compile time concatenation in macros. We will allow it for now" }, | 78 | { id = "RUSTSEC-2024-0436", reason = "there are no suitable replacements for paste right now; paste has been archived as read-only. It only affects compile time concatenation in macros. We will allow it for now" }, |
| 79 | # { id = "RUSTSEC-2023-0089", reason = "this is a deprecation warning for a dependency of a dependency. https://github.com/jamesmunns/postcard/issues/223 tracks fixing the dependency; until that's resolved, we can accept the deprecated code as it has no known vulnerabilities."} | ||
| 79 | ] | 80 | ] |
| 80 | # If this is true, then cargo deny will use the git executable to fetch advisory database. | 81 | # If this is true, then cargo deny will use the git executable to fetch advisory database. |
| 81 | # If this is false, then it uses a built-in git library. | 82 | # If this is false, then it uses a built-in git library. |
| @@ -93,8 +94,9 @@ ignore = [ | |||
| 93 | allow = [ | 94 | allow = [ |
| 94 | "MIT", | 95 | "MIT", |
| 95 | "Apache-2.0", | 96 | "Apache-2.0", |
| 97 | |||
| 98 | # unicode-ident 1.0.14 switched from Unicode-DFS-2016 to Unicode-3.0 license. | ||
| 96 | "Unicode-3.0", | 99 | "Unicode-3.0", |
| 97 | "BSD-3-Clause", | ||
| 98 | #"Apache-2.0 WITH LLVM-exception", | 100 | #"Apache-2.0 WITH LLVM-exception", |
| 99 | ] | 101 | ] |
| 100 | # The confidence threshold for detecting a license from license text. | 102 | # The confidence threshold for detecting a license from license text. |
| @@ -232,7 +234,7 @@ allow-git = [] | |||
| 232 | 234 | ||
| 233 | [sources.allow-org] | 235 | [sources.allow-org] |
| 234 | # github.com organizations to allow git sources for | 236 | # github.com organizations to allow git sources for |
| 235 | github = ["embassy-rs"] | 237 | github = ["OpenDevicePartnership"] |
| 236 | # gitlab.com organizations to allow git sources for | 238 | # gitlab.com organizations to allow git sources for |
| 237 | gitlab = [] | 239 | gitlab = [] |
| 238 | # bitbucket.org organizations to allow git sources for | 240 | # bitbucket.org organizations to allow git sources for |
diff --git a/examples/adc_interrupt.rs b/examples/adc_interrupt.rs new file mode 100644 index 000000000..f0df3196c --- /dev/null +++ b/examples/adc_interrupt.rs | |||
| @@ -0,0 +1,89 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | |||
| 4 | use embassy_executor::Spawner; | ||
| 5 | use hal::adc::{LpadcConfig, TriggerPriorityPolicy}; | ||
| 6 | use hal::pac::adc1::cfg::{Pwrsel, Refsel}; | ||
| 7 | use hal::pac::adc1::cmdl1::{Adch, Mode}; | ||
| 8 | use hal::pac::adc1::ctrl::CalAvgs; | ||
| 9 | use hal::pac::adc1::tctrl::Tcmd; | ||
| 10 | use hal::uart; | ||
| 11 | use {cortex_m, embassy_mcxa276 as hal}; | ||
| 12 | mod common; | ||
| 13 | |||
| 14 | use hal::{bind_interrupts, InterruptExt}; | ||
| 15 | use {defmt_rtt as _, panic_probe as _}; | ||
| 16 | |||
| 17 | bind_interrupts!(struct Irqs { | ||
| 18 | ADC1 => hal::adc::AdcHandler; | ||
| 19 | }); | ||
| 20 | |||
| 21 | #[used] | ||
| 22 | #[no_mangle] | ||
| 23 | static KEEP_ADC: unsafe extern "C" fn() = ADC1; | ||
| 24 | |||
| 25 | #[embassy_executor::main] | ||
| 26 | async fn main(_spawner: Spawner) { | ||
| 27 | let p = hal::init(hal::config::Config::default()); | ||
| 28 | |||
| 29 | unsafe { | ||
| 30 | common::init_uart2(hal::pac()); | ||
| 31 | } | ||
| 32 | |||
| 33 | let src = unsafe { hal::clocks::uart2_src_hz(hal::pac()) }; | ||
| 34 | let uart = uart::Uart::<uart::Lpuart2>::new(p.LPUART2, uart::Config::new(src)); | ||
| 35 | |||
| 36 | uart.write_str_blocking("\r\n=== ADC interrupt Example ===\r\n"); | ||
| 37 | |||
| 38 | unsafe { | ||
| 39 | common::init_adc(hal::pac()); | ||
| 40 | } | ||
| 41 | |||
| 42 | let adc_config = LpadcConfig { | ||
| 43 | enable_in_doze_mode: true, | ||
| 44 | conversion_average_mode: CalAvgs::Average128, | ||
| 45 | enable_analog_preliminary: true, | ||
| 46 | power_up_delay: 0x80, | ||
| 47 | reference_voltage_source: Refsel::Option3, | ||
| 48 | power_level_mode: Pwrsel::Lowest, | ||
| 49 | trigger_priority_policy: TriggerPriorityPolicy::ConvPreemptImmediatelyNotAutoResumed, | ||
| 50 | enable_conv_pause: false, | ||
| 51 | conv_pause_delay: 0, | ||
| 52 | fifo_watermark: 0, | ||
| 53 | }; | ||
| 54 | let adc = hal::adc::Adc::<hal::adc::Adc1>::new(p.ADC1, adc_config); | ||
| 55 | |||
| 56 | adc.do_offset_calibration(); | ||
| 57 | adc.do_auto_calibration(); | ||
| 58 | |||
| 59 | let mut conv_command_config = adc.get_default_conv_command_config(); | ||
| 60 | conv_command_config.channel_number = Adch::SelectCorrespondingChannel8; | ||
| 61 | conv_command_config.conversion_resolution_mode = Mode::Data16Bits; | ||
| 62 | adc.set_conv_command_config(1, &conv_command_config); | ||
| 63 | |||
| 64 | let mut conv_trigger_config = adc.get_default_conv_trigger_config(); | ||
| 65 | conv_trigger_config.target_command_id = Tcmd::ExecuteCmd1; | ||
| 66 | conv_trigger_config.enable_hardware_trigger = false; | ||
| 67 | adc.set_conv_trigger_config(0, &conv_trigger_config); | ||
| 68 | |||
| 69 | uart.write_str_blocking("\r\n=== ADC configuration done... ===\r\n"); | ||
| 70 | |||
| 71 | adc.enable_interrupt(0x1); | ||
| 72 | |||
| 73 | unsafe { | ||
| 74 | hal::interrupt::ADC1.enable(); | ||
| 75 | } | ||
| 76 | |||
| 77 | unsafe { | ||
| 78 | cortex_m::interrupt::enable(); | ||
| 79 | } | ||
| 80 | |||
| 81 | loop { | ||
| 82 | adc.do_software_trigger(1); | ||
| 83 | while !adc.is_interrupt_triggered() { | ||
| 84 | // Wait until the interrupt is triggered | ||
| 85 | } | ||
| 86 | uart.write_str_blocking("\r\n*** ADC interrupt TRIGGERED! ***\r\n"); | ||
| 87 | //TBD need to print the value | ||
| 88 | } | ||
| 89 | } | ||
diff --git a/examples/adc_polling.rs b/examples/adc_polling.rs new file mode 100644 index 000000000..561500d2d --- /dev/null +++ b/examples/adc_polling.rs | |||
| @@ -0,0 +1,79 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | |||
| 4 | use embassy_executor::Spawner; | ||
| 5 | use embassy_mcxa276 as hal; | ||
| 6 | use hal::adc::{ConvResult, LpadcConfig, TriggerPriorityPolicy}; | ||
| 7 | use hal::pac::adc1::cfg::{Pwrsel, Refsel}; | ||
| 8 | use hal::pac::adc1::cmdl1::{Adch, Mode}; | ||
| 9 | use hal::pac::adc1::ctrl::CalAvgs; | ||
| 10 | use hal::pac::adc1::tctrl::Tcmd; | ||
| 11 | use hal::uart; | ||
| 12 | |||
| 13 | mod common; | ||
| 14 | |||
| 15 | use core::fmt::Write; | ||
| 16 | |||
| 17 | use heapless::String; | ||
| 18 | use {defmt_rtt as _, panic_probe as _}; | ||
| 19 | |||
| 20 | const G_LPADC_RESULT_SHIFT: u32 = 0; | ||
| 21 | |||
| 22 | #[embassy_executor::main] | ||
| 23 | async fn main(_spawner: Spawner) { | ||
| 24 | let p = hal::init(hal::config::Config::default()); | ||
| 25 | |||
| 26 | unsafe { | ||
| 27 | common::init_uart2(hal::pac()); | ||
| 28 | } | ||
| 29 | |||
| 30 | let src = unsafe { hal::clocks::uart2_src_hz(hal::pac()) }; | ||
| 31 | let uart = uart::Uart::<uart::Lpuart2>::new(p.LPUART2, uart::Config::new(src)); | ||
| 32 | |||
| 33 | uart.write_str_blocking("\r\n=== ADC polling Example ===\r\n"); | ||
| 34 | |||
| 35 | unsafe { | ||
| 36 | common::init_adc(hal::pac()); | ||
| 37 | } | ||
| 38 | |||
| 39 | let adc_config = LpadcConfig { | ||
| 40 | enable_in_doze_mode: true, | ||
| 41 | conversion_average_mode: CalAvgs::Average128, | ||
| 42 | enable_analog_preliminary: true, | ||
| 43 | power_up_delay: 0x80, | ||
| 44 | reference_voltage_source: Refsel::Option3, | ||
| 45 | power_level_mode: Pwrsel::Lowest, | ||
| 46 | trigger_priority_policy: TriggerPriorityPolicy::ConvPreemptImmediatelyNotAutoResumed, | ||
| 47 | enable_conv_pause: false, | ||
| 48 | conv_pause_delay: 0, | ||
| 49 | fifo_watermark: 0, | ||
| 50 | }; | ||
| 51 | let adc = hal::adc::Adc::<hal::adc::Adc1>::new(p.ADC1, adc_config); | ||
| 52 | |||
| 53 | adc.do_offset_calibration(); | ||
| 54 | adc.do_auto_calibration(); | ||
| 55 | |||
| 56 | let mut conv_command_config = adc.get_default_conv_command_config(); | ||
| 57 | conv_command_config.channel_number = Adch::SelectCorrespondingChannel8; | ||
| 58 | conv_command_config.conversion_resolution_mode = Mode::Data16Bits; | ||
| 59 | adc.set_conv_command_config(1, &conv_command_config); | ||
| 60 | |||
| 61 | let mut conv_trigger_config = adc.get_default_conv_trigger_config(); | ||
| 62 | conv_trigger_config.target_command_id = Tcmd::ExecuteCmd1; | ||
| 63 | conv_trigger_config.enable_hardware_trigger = false; | ||
| 64 | adc.set_conv_trigger_config(0, &conv_trigger_config); | ||
| 65 | |||
| 66 | uart.write_str_blocking("\r\n=== ADC configuration done... ===\r\n"); | ||
| 67 | |||
| 68 | loop { | ||
| 69 | adc.do_software_trigger(1); | ||
| 70 | let mut result: Option<ConvResult> = None; | ||
| 71 | while result.is_none() { | ||
| 72 | result = hal::adc::get_conv_result(); | ||
| 73 | } | ||
| 74 | let value = result.unwrap().conv_value >> G_LPADC_RESULT_SHIFT; | ||
| 75 | let mut buf: String<16> = String::new(); // adjust size as needed | ||
| 76 | write!(buf, "\r\nvalue: {}\r\n", value).unwrap(); | ||
| 77 | uart.write_str_blocking(&buf); | ||
| 78 | } | ||
| 79 | } | ||
diff --git a/examples/blink.rs b/examples/blink.rs new file mode 100644 index 000000000..564353d5c --- /dev/null +++ b/examples/blink.rs | |||
| @@ -0,0 +1,84 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | |||
| 4 | use embassy_executor::Spawner; | ||
| 5 | use embassy_mcxa276 as hal; | ||
| 6 | use embassy_time::{Duration, Timer}; | ||
| 7 | use hal::gpio::pins::PIO3_18; | ||
| 8 | use hal::gpio::{Level, Output}; | ||
| 9 | |||
| 10 | mod common; | ||
| 11 | |||
| 12 | use embassy_mcxa276::bind_interrupts; | ||
| 13 | |||
| 14 | // Bind only OS_EVENT for timer interrupts | ||
| 15 | bind_interrupts!(struct Irqs { | ||
| 16 | OS_EVENT => hal::ostimer::time_driver::OsEventHandler; | ||
| 17 | }); | ||
| 18 | |||
| 19 | #[used] | ||
| 20 | #[no_mangle] | ||
| 21 | static KEEP_OS_EVENT: unsafe extern "C" fn() = OS_EVENT; | ||
| 22 | |||
| 23 | #[embassy_executor::main] | ||
| 24 | async fn main(_spawner: Spawner) { | ||
| 25 | let _p = hal::init(hal::config::Config::default()); | ||
| 26 | |||
| 27 | // Board-style init: enable LED GPIO/PORT clocks used by blink | ||
| 28 | unsafe { | ||
| 29 | common::init_led(hal::pac()); | ||
| 30 | } | ||
| 31 | // Initialize OSTIMER for async timing | ||
| 32 | unsafe { | ||
| 33 | common::init_ostimer0(hal::pac()); | ||
| 34 | } | ||
| 35 | |||
| 36 | // Initialize embassy-time global driver backed by OSTIMER0 | ||
| 37 | hal::ostimer::time_driver::init(hal::config::Config::default().time_interrupt_priority, 1_000_000); | ||
| 38 | |||
| 39 | // Configure LED pin for GPIO mode | ||
| 40 | PIO3_18::set_mux_gpio(); | ||
| 41 | |||
| 42 | let mut led = Output::new(PIO3_18::degrade(), Level::High); | ||
| 43 | |||
| 44 | // Complex blinking pattern: SOS in Morse code | ||
| 45 | // S: ... (3 short) | ||
| 46 | // O: --- (3 long) | ||
| 47 | // S: ... (3 short) | ||
| 48 | // With pauses between letters and words | ||
| 49 | |||
| 50 | loop { | ||
| 51 | // S: three short blinks | ||
| 52 | for _ in 0..3 { | ||
| 53 | led.set_low(); | ||
| 54 | Timer::after(Duration::from_millis(150)).await; | ||
| 55 | led.set_high(); | ||
| 56 | Timer::after(Duration::from_millis(150)).await; | ||
| 57 | } | ||
| 58 | |||
| 59 | // Pause between letters | ||
| 60 | Timer::after(Duration::from_millis(300)).await; | ||
| 61 | |||
| 62 | // O: three long blinks | ||
| 63 | for _ in 0..3 { | ||
| 64 | led.set_low(); | ||
| 65 | Timer::after(Duration::from_millis(450)).await; | ||
| 66 | led.set_high(); | ||
| 67 | Timer::after(Duration::from_millis(150)).await; | ||
| 68 | } | ||
| 69 | |||
| 70 | // Pause between letters | ||
| 71 | Timer::after(Duration::from_millis(300)).await; | ||
| 72 | |||
| 73 | // S: three short blinks | ||
| 74 | for _ in 0..3 { | ||
| 75 | led.set_low(); | ||
| 76 | Timer::after(Duration::from_millis(150)).await; | ||
| 77 | led.set_high(); | ||
| 78 | Timer::after(Duration::from_millis(150)).await; | ||
| 79 | } | ||
| 80 | |||
| 81 | // Long pause between words (SOS repeats) | ||
| 82 | Timer::after(Duration::from_millis(1000)).await; | ||
| 83 | } | ||
| 84 | } | ||
diff --git a/examples/common/mod.rs b/examples/common/mod.rs new file mode 100644 index 000000000..7ada4c456 --- /dev/null +++ b/examples/common/mod.rs | |||
| @@ -0,0 +1,45 @@ | |||
| 1 | //! Shared board-specific helpers for the FRDM-MCXA276 examples. | ||
| 2 | //! These live with the examples so the HAL stays generic. | ||
| 3 | |||
| 4 | use embassy_mcxa276 as hal; | ||
| 5 | use hal::{clocks, pins, reset}; | ||
| 6 | |||
| 7 | /// Initialize clocks and pin muxing for UART2 debug console. | ||
| 8 | /// Safe to call multiple times; writes are idempotent for our use. | ||
| 9 | #[allow(dead_code)] | ||
| 10 | pub unsafe fn init_uart2(p: &hal::pac::Peripherals) { | ||
| 11 | clocks::ensure_frolf_running(p); | ||
| 12 | clocks::enable_uart2_port2(p); | ||
| 13 | reset::release_reset_port2(p); | ||
| 14 | reset::release_reset_lpuart2(p); | ||
| 15 | pins::configure_uart2_pins_port2(); | ||
| 16 | clocks::select_uart2_clock(p); | ||
| 17 | } | ||
| 18 | |||
| 19 | /// Initialize clocks for the LED GPIO/PORT used by the blink example. | ||
| 20 | #[allow(dead_code)] | ||
| 21 | pub unsafe fn init_led(p: &hal::pac::Peripherals) { | ||
| 22 | clocks::enable_led_port(p); | ||
| 23 | reset::release_reset_gpio3(p); | ||
| 24 | reset::release_reset_port3(p); | ||
| 25 | } | ||
| 26 | |||
| 27 | /// Initialize clocks for OSTIMER0 (1 MHz source). | ||
| 28 | #[allow(dead_code)] | ||
| 29 | pub unsafe fn init_ostimer0(p: &hal::pac::Peripherals) { | ||
| 30 | clocks::ensure_frolf_running(p); | ||
| 31 | clocks::enable_ostimer0(p); | ||
| 32 | reset::release_reset_ostimer0(p); | ||
| 33 | clocks::select_ostimer0_clock_1m(p); | ||
| 34 | } | ||
| 35 | |||
| 36 | /// Initialize clocks and pin muxing for ADC. | ||
| 37 | #[allow(dead_code)] | ||
| 38 | pub unsafe fn init_adc(p: &hal::pac::Peripherals) { | ||
| 39 | clocks::ensure_frolf_running(p); | ||
| 40 | clocks::enable_adc(p); | ||
| 41 | reset::release_reset_port1(p); | ||
| 42 | reset::release_reset_adc1(p); | ||
| 43 | pins::configure_adc_pins(); | ||
| 44 | clocks::select_adc_clock(p); | ||
| 45 | } | ||
diff --git a/examples/hello.rs b/examples/hello.rs new file mode 100644 index 000000000..e39adaced --- /dev/null +++ b/examples/hello.rs | |||
| @@ -0,0 +1,114 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | |||
| 4 | use embassy_executor::Spawner; | ||
| 5 | use embassy_mcxa276 as hal; | ||
| 6 | use hal::uart; | ||
| 7 | |||
| 8 | mod common; | ||
| 9 | |||
| 10 | use {defmt_rtt as _, panic_probe as _}; | ||
| 11 | |||
| 12 | /// Simple helper to write a byte as hex to UART | ||
| 13 | fn write_hex_byte(uart: &hal::uart::Uart<hal::uart::Lpuart2>, byte: u8) { | ||
| 14 | const HEX_DIGITS: &[u8] = b"0123456789ABCDEF"; | ||
| 15 | uart.write_byte(HEX_DIGITS[(byte >> 4) as usize]); | ||
| 16 | uart.write_byte(HEX_DIGITS[(byte & 0xF) as usize]); | ||
| 17 | } | ||
| 18 | |||
| 19 | #[embassy_executor::main] | ||
| 20 | async fn main(_spawner: Spawner) { | ||
| 21 | let p = hal::init(hal::config::Config::default()); | ||
| 22 | |||
| 23 | defmt::info!("boot"); | ||
| 24 | |||
| 25 | // Board-level init for UART2 clocks and pins. | ||
| 26 | unsafe { | ||
| 27 | common::init_uart2(hal::pac()); | ||
| 28 | } | ||
| 29 | |||
| 30 | // Get UART source frequency from clock configuration | ||
| 31 | // Using hardcoded frequency for now - dynamic detection may have issues | ||
| 32 | let src = 12_000_000; // FRO_LF_DIV at 12MHz with DIV=0 | ||
| 33 | let uart = uart::Uart::<uart::Lpuart2>::new(p.LPUART2, uart::Config::new(src)); | ||
| 34 | |||
| 35 | // Print welcome message before any async delays to guarantee early console output | ||
| 36 | uart.write_str_blocking("\r\n=== MCXA276 UART Echo Demo ===\r\n"); | ||
| 37 | uart.write_str_blocking("Available commands:\r\n"); | ||
| 38 | uart.write_str_blocking(" help - Show this help\r\n"); | ||
| 39 | uart.write_str_blocking(" echo <text> - Echo back the text\r\n"); | ||
| 40 | uart.write_str_blocking(" hex <byte> - Display byte in hex (0-255)\r\n"); | ||
| 41 | uart.write_str_blocking("Type a command: "); | ||
| 42 | |||
| 43 | let mut buffer = [0u8; 64]; | ||
| 44 | let mut buf_idx = 0; | ||
| 45 | |||
| 46 | loop { | ||
| 47 | // Read a byte from UART | ||
| 48 | let byte = uart.read_byte_blocking(); | ||
| 49 | |||
| 50 | // Echo the character back | ||
| 51 | if byte == b'\r' || byte == b'\n' { | ||
| 52 | // Enter pressed - process command | ||
| 53 | uart.write_str_blocking("\r\n"); | ||
| 54 | |||
| 55 | if buf_idx > 0 { | ||
| 56 | let command = &buffer[0..buf_idx]; | ||
| 57 | |||
| 58 | if command == b"help" { | ||
| 59 | uart.write_str_blocking("Available commands:\r\n"); | ||
| 60 | uart.write_str_blocking(" help - Show this help\r\n"); | ||
| 61 | uart.write_str_blocking(" echo <text> - Echo back the text\r\n"); | ||
| 62 | uart.write_str_blocking(" hex <byte> - Display byte in hex (0-255)\r\n"); | ||
| 63 | } else if command.starts_with(b"echo ") && command.len() > 5 { | ||
| 64 | uart.write_str_blocking("Echo: "); | ||
| 65 | uart.write_str_blocking(core::str::from_utf8(&command[5..]).unwrap_or("")); | ||
| 66 | uart.write_str_blocking("\r\n"); | ||
| 67 | } else if command.starts_with(b"hex ") && command.len() > 4 { | ||
| 68 | // Parse the byte value | ||
| 69 | let num_str = &command[4..]; | ||
| 70 | if let Ok(num) = parse_u8(num_str) { | ||
| 71 | uart.write_str_blocking("Hex: 0x"); | ||
| 72 | write_hex_byte(&uart, num); | ||
| 73 | uart.write_str_blocking("\r\n"); | ||
| 74 | } else { | ||
| 75 | uart.write_str_blocking("Invalid number for hex command\r\n"); | ||
| 76 | } | ||
| 77 | } else if command.len() > 0 { | ||
| 78 | uart.write_str_blocking("Unknown command: "); | ||
| 79 | uart.write_str_blocking(core::str::from_utf8(command).unwrap_or("")); | ||
| 80 | uart.write_str_blocking("\r\n"); | ||
| 81 | } | ||
| 82 | } | ||
| 83 | |||
| 84 | // Reset buffer and prompt | ||
| 85 | buf_idx = 0; | ||
| 86 | uart.write_str_blocking("Type a command: "); | ||
| 87 | } else if byte == 8 || byte == 127 { | ||
| 88 | // Backspace | ||
| 89 | if buf_idx > 0 { | ||
| 90 | buf_idx -= 1; | ||
| 91 | uart.write_str_blocking("\x08 \x08"); // Erase character | ||
| 92 | } | ||
| 93 | } else if buf_idx < buffer.len() - 1 { | ||
| 94 | // Regular character | ||
| 95 | buffer[buf_idx] = byte; | ||
| 96 | buf_idx += 1; | ||
| 97 | uart.write_byte(byte); | ||
| 98 | } | ||
| 99 | } | ||
| 100 | } | ||
| 101 | |||
| 102 | /// Simple parser for u8 from ASCII bytes | ||
| 103 | fn parse_u8(bytes: &[u8]) -> Result<u8, ()> { | ||
| 104 | let mut result = 0u8; | ||
| 105 | for &b in bytes { | ||
| 106 | if b >= b'0' && b <= b'9' { | ||
| 107 | result = result.checked_mul(10).ok_or(())?; | ||
| 108 | result = result.checked_add(b - b'0').ok_or(())?; | ||
| 109 | } else { | ||
| 110 | return Err(()); | ||
| 111 | } | ||
| 112 | } | ||
| 113 | Ok(result) | ||
| 114 | } | ||
diff --git a/examples/lpuart_buffered.rs b/examples/lpuart_buffered.rs new file mode 100644 index 000000000..30ba3f333 --- /dev/null +++ b/examples/lpuart_buffered.rs | |||
| @@ -0,0 +1,77 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | |||
| 4 | use embassy_executor::Spawner; | ||
| 5 | use embassy_mcxa276 as hal; | ||
| 6 | use embassy_mcxa276::interrupt::typelevel::Handler; | ||
| 7 | use embassy_mcxa276::lpuart::buffered::BufferedLpuart; | ||
| 8 | use embassy_mcxa276::{bind_interrupts, lpuart}; | ||
| 9 | use embedded_io_async::{Read, Write}; | ||
| 10 | |||
| 11 | mod common; | ||
| 12 | |||
| 13 | // Bind OS_EVENT for timers plus LPUART2 IRQ for the buffered driver | ||
| 14 | bind_interrupts!(struct Irqs { | ||
| 15 | LPUART2 => lpuart::buffered::BufferedInterruptHandler::<lpuart::lib::peripherals::LPUART2>; | ||
| 16 | }); | ||
| 17 | |||
| 18 | // Wrapper function for the interrupt handler | ||
| 19 | unsafe extern "C" fn lpuart2_handler() { | ||
| 20 | lpuart::buffered::BufferedInterruptHandler::<lpuart::lib::peripherals::LPUART2>::on_interrupt(); | ||
| 21 | } | ||
| 22 | |||
| 23 | #[embassy_executor::main] | ||
| 24 | async fn main(_spawner: Spawner) { | ||
| 25 | let _p = hal::init(hal::config::Config::default()); | ||
| 26 | let p2 = lpuart::lib::init(); | ||
| 27 | |||
| 28 | unsafe { | ||
| 29 | hal::interrupt::install_irq_handler(mcxa276_pac::Interrupt::LPUART2, lpuart2_handler); | ||
| 30 | } | ||
| 31 | |||
| 32 | // Configure NVIC for LPUART2 | ||
| 33 | hal::interrupt::LPUART2.configure_for_uart(hal::interrupt::Priority::P3); | ||
| 34 | |||
| 35 | unsafe { | ||
| 36 | common::init_uart2(hal::pac()); | ||
| 37 | common::init_ostimer0(hal::pac()); | ||
| 38 | } | ||
| 39 | |||
| 40 | // UART configuration (enable both TX and RX) | ||
| 41 | let config = lpuart::Config { | ||
| 42 | baudrate_bps: 115_200, | ||
| 43 | enable_tx: true, | ||
| 44 | enable_rx: true, | ||
| 45 | rx_fifo_watermark: 0, | ||
| 46 | tx_fifo_watermark: 0, | ||
| 47 | ..Default::default() | ||
| 48 | }; | ||
| 49 | |||
| 50 | let mut tx_buf = [0u8; 256]; | ||
| 51 | let mut rx_buf = [0u8; 256]; | ||
| 52 | |||
| 53 | // Create a buffered LPUART2 instance with both TX and RX | ||
| 54 | let mut uart = BufferedLpuart::new( | ||
| 55 | p2.LPUART2, | ||
| 56 | p2.PIO2_2, // TX pin | ||
| 57 | p2.PIO2_3, // RX pin | ||
| 58 | Irqs, | ||
| 59 | &mut tx_buf, | ||
| 60 | &mut rx_buf, | ||
| 61 | config, | ||
| 62 | ) | ||
| 63 | .unwrap(); | ||
| 64 | |||
| 65 | // Split into TX and RX parts | ||
| 66 | let (tx, rx) = uart.split_ref(); | ||
| 67 | |||
| 68 | tx.write(b"Hello buffered LPUART.\r\n").await.unwrap(); | ||
| 69 | tx.write(b"Type characters to echo them back.\r\n").await.unwrap(); | ||
| 70 | |||
| 71 | // Echo loop | ||
| 72 | let mut buf = [0u8; 4]; | ||
| 73 | loop { | ||
| 74 | rx.read_exact(&mut buf[..]).await.unwrap(); | ||
| 75 | tx.write_all(&buf[..]).await.unwrap(); | ||
| 76 | } | ||
| 77 | } | ||
diff --git a/examples/lpuart_polling.rs b/examples/lpuart_polling.rs new file mode 100644 index 000000000..067c7eb53 --- /dev/null +++ b/examples/lpuart_polling.rs | |||
| @@ -0,0 +1,53 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | |||
| 4 | use embassy_executor::Spawner; | ||
| 5 | use {defmt_rtt as _, embassy_mcxa276 as hal, panic_probe as _}; | ||
| 6 | |||
| 7 | use crate::hal::lpuart::{lib, Config, Lpuart}; | ||
| 8 | |||
| 9 | mod common; | ||
| 10 | |||
| 11 | #[embassy_executor::main] | ||
| 12 | async fn main(_spawner: Spawner) { | ||
| 13 | let _p = hal::init(hal::config::Config::default()); | ||
| 14 | let p2 = lib::init(); | ||
| 15 | |||
| 16 | defmt::info!("boot"); | ||
| 17 | |||
| 18 | // Board-level init for UART2 clocks and pins. | ||
| 19 | unsafe { | ||
| 20 | common::init_uart2(hal::pac()); | ||
| 21 | } | ||
| 22 | |||
| 23 | // Create UART configuration | ||
| 24 | let config = Config { | ||
| 25 | baudrate_bps: 115_200, | ||
| 26 | enable_tx: true, | ||
| 27 | enable_rx: true, | ||
| 28 | ..Default::default() | ||
| 29 | }; | ||
| 30 | |||
| 31 | // Create UART instance using LPUART2 with PIO2_2 as TX and PIO2_3 as RX | ||
| 32 | let lpuart = Lpuart::new_blocking( | ||
| 33 | p2.LPUART2, // Peripheral | ||
| 34 | p2.PIO2_2, // TX pin | ||
| 35 | p2.PIO2_3, // RX pin | ||
| 36 | config, | ||
| 37 | ) | ||
| 38 | .unwrap(); | ||
| 39 | |||
| 40 | // Split into separate TX and RX parts | ||
| 41 | let (mut tx, mut rx) = lpuart.split(); | ||
| 42 | |||
| 43 | // Write hello messages | ||
| 44 | tx.blocking_write(b"Hello world.\r\n").unwrap(); | ||
| 45 | tx.blocking_write(b"Echoing. Type characters...\r\n").unwrap(); | ||
| 46 | |||
| 47 | // Echo loop | ||
| 48 | loop { | ||
| 49 | let mut buf = [0u8; 1]; | ||
| 50 | rx.blocking_read(&mut buf).unwrap(); | ||
| 51 | tx.blocking_write(&buf).unwrap(); | ||
| 52 | } | ||
| 53 | } | ||
diff --git a/examples/ostimer_alarm.rs b/examples/ostimer_alarm.rs new file mode 100644 index 000000000..78ca4bbc5 --- /dev/null +++ b/examples/ostimer_alarm.rs | |||
| @@ -0,0 +1,111 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | |||
| 4 | use core::sync::atomic::{AtomicBool, Ordering}; | ||
| 5 | |||
| 6 | use embassy_executor::Spawner; | ||
| 7 | use hal::uart; | ||
| 8 | use {cortex_m, embassy_mcxa276 as hal}; | ||
| 9 | |||
| 10 | mod common; | ||
| 11 | |||
| 12 | use embassy_mcxa276::bind_interrupts; | ||
| 13 | use {defmt_rtt as _, panic_probe as _}; | ||
| 14 | |||
| 15 | // Bind only OS_EVENT, and retain the symbol explicitly so it can't be GC'ed. | ||
| 16 | bind_interrupts!(struct Irqs { | ||
| 17 | OS_EVENT => hal::ostimer::time_driver::OsEventHandler; | ||
| 18 | }); | ||
| 19 | |||
| 20 | #[used] | ||
| 21 | #[no_mangle] | ||
| 22 | static KEEP_OS_EVENT: unsafe extern "C" fn() = OS_EVENT; | ||
| 23 | |||
| 24 | // Global flag for alarm callback | ||
| 25 | static ALARM_FLAG: AtomicBool = AtomicBool::new(false); | ||
| 26 | |||
| 27 | // Alarm callback function | ||
| 28 | fn alarm_callback() { | ||
| 29 | ALARM_FLAG.store(true, Ordering::Release); | ||
| 30 | } | ||
| 31 | |||
| 32 | #[embassy_executor::main] | ||
| 33 | async fn main(_spawner: Spawner) { | ||
| 34 | let p = hal::init(hal::config::Config::default()); | ||
| 35 | |||
| 36 | // Enable/clock OSTIMER0 and UART2 before touching their registers | ||
| 37 | unsafe { | ||
| 38 | common::init_ostimer0(hal::pac()); | ||
| 39 | } | ||
| 40 | unsafe { | ||
| 41 | common::init_uart2(hal::pac()); | ||
| 42 | } | ||
| 43 | let src = unsafe { hal::clocks::uart2_src_hz(hal::pac()) }; | ||
| 44 | let uart = uart::Uart::<uart::Lpuart2>::new(p.LPUART2, uart::Config::new(src)); | ||
| 45 | uart.write_str_blocking("OSTIMER Alarm Example\n"); | ||
| 46 | |||
| 47 | // Initialize embassy-time global driver backed by OSTIMER0 | ||
| 48 | hal::ostimer::time_driver::init(hal::config::Config::default().time_interrupt_priority, 1_000_000); | ||
| 49 | |||
| 50 | // Create OSTIMER instance | ||
| 51 | let config = hal::ostimer::Config { | ||
| 52 | init_match_max: true, | ||
| 53 | clock_frequency_hz: 1_000_000, // 1MHz | ||
| 54 | }; | ||
| 55 | let ostimer = hal::ostimer::Ostimer::<hal::ostimer::Ostimer0>::new(p.OSTIMER0, config, hal::pac()); | ||
| 56 | |||
| 57 | // Create alarm with callback | ||
| 58 | let alarm = hal::ostimer::Alarm::new() | ||
| 59 | .with_callback(alarm_callback) | ||
| 60 | .with_flag(&ALARM_FLAG); | ||
| 61 | |||
| 62 | uart.write_str_blocking("Scheduling alarm for 2 seconds...\n"); | ||
| 63 | |||
| 64 | // Schedule alarm to expire in 2 seconds (2,000,000 microseconds) | ||
| 65 | let scheduled = ostimer.schedule_alarm_delay(&alarm, 2_000_000); | ||
| 66 | if scheduled { | ||
| 67 | uart.write_str_blocking("Alarm scheduled successfully\n"); | ||
| 68 | } else { | ||
| 69 | uart.write_str_blocking("Failed to schedule alarm (would exceed timer capacity)\n"); | ||
| 70 | return; | ||
| 71 | } | ||
| 72 | |||
| 73 | // Wait for alarm to expire | ||
| 74 | loop { | ||
| 75 | // Check if alarm has expired | ||
| 76 | if ALARM_FLAG.load(Ordering::Acquire) { | ||
| 77 | uart.write_str_blocking("Alarm expired! Callback executed.\n"); | ||
| 78 | break; | ||
| 79 | } | ||
| 80 | |||
| 81 | // Busy wait - don't use Timer::after_millis as it interferes with alarm MATCH | ||
| 82 | for _ in 0..100000 { | ||
| 83 | cortex_m::asm::nop(); | ||
| 84 | } | ||
| 85 | } | ||
| 86 | |||
| 87 | // Demonstrate canceling an alarm | ||
| 88 | uart.write_str_blocking("Scheduling another alarm for 3 seconds...\n"); | ||
| 89 | ALARM_FLAG.store(false, Ordering::Release); // Reset flag | ||
| 90 | |||
| 91 | let scheduled = ostimer.schedule_alarm_delay(&alarm, 3_000_000); | ||
| 92 | if scheduled { | ||
| 93 | uart.write_str_blocking("Alarm scheduled. Waiting 1 second then canceling...\n"); | ||
| 94 | |||
| 95 | // Wait 1 second | ||
| 96 | embassy_time::Timer::after_millis(1000).await; | ||
| 97 | |||
| 98 | // Cancel the alarm | ||
| 99 | ostimer.cancel_alarm(&alarm); | ||
| 100 | uart.write_str_blocking("Alarm canceled\n"); | ||
| 101 | |||
| 102 | // Check immediately if alarm flag is set | ||
| 103 | if !ALARM_FLAG.load(Ordering::Acquire) { | ||
| 104 | uart.write_str_blocking("Alarm was successfully canceled\n"); | ||
| 105 | } else { | ||
| 106 | uart.write_str_blocking("Alarm fired despite cancellation\n"); | ||
| 107 | } | ||
| 108 | } | ||
| 109 | |||
| 110 | uart.write_str_blocking("Example complete\n"); | ||
| 111 | } | ||
diff --git a/examples/ostimer_async.rs b/examples/ostimer_async.rs new file mode 100644 index 000000000..27e14e022 --- /dev/null +++ b/examples/ostimer_async.rs | |||
| @@ -0,0 +1,57 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | |||
| 4 | use embassy_executor::Spawner; | ||
| 5 | use embassy_mcxa276 as hal; | ||
| 6 | use hal::uart; | ||
| 7 | |||
| 8 | mod common; | ||
| 9 | |||
| 10 | use embassy_mcxa276::bind_interrupts; | ||
| 11 | use embassy_time::{Duration, Timer}; | ||
| 12 | use {defmt_rtt as _, panic_probe as _}; | ||
| 13 | |||
| 14 | // Bind only OS_EVENT, and retain the symbol explicitly so it can’t be GC’ed. | ||
| 15 | bind_interrupts!(struct Irqs { | ||
| 16 | OS_EVENT => hal::ostimer::time_driver::OsEventHandler; | ||
| 17 | }); | ||
| 18 | |||
| 19 | #[used] | ||
| 20 | #[no_mangle] | ||
| 21 | static KEEP_OS_EVENT: unsafe extern "C" fn() = OS_EVENT; | ||
| 22 | |||
| 23 | #[embassy_executor::main] | ||
| 24 | async fn main(_spawner: Spawner) { | ||
| 25 | let _p = hal::init(hal::config::Config::default()); | ||
| 26 | |||
| 27 | // Enable/clock OSTIMER0 and UART2 before touching their registers | ||
| 28 | unsafe { | ||
| 29 | common::init_ostimer0(hal::pac()); | ||
| 30 | } | ||
| 31 | unsafe { | ||
| 32 | common::init_uart2(hal::pac()); | ||
| 33 | } | ||
| 34 | let src = unsafe { hal::clocks::uart2_src_hz(hal::pac()) }; | ||
| 35 | let uart = uart::Uart::<uart::Lpuart2>::new(_p.LPUART2, uart::Config::new(src)); | ||
| 36 | uart.write_str_blocking("boot\n"); | ||
| 37 | |||
| 38 | // Avoid mass NVIC writes here; DefaultHandler now safely returns. | ||
| 39 | |||
| 40 | // Initialize embassy-time global driver backed by OSTIMER0 (re-enables OS_EVENT with priority) | ||
| 41 | // The bind_interrupts! macro handles handler binding automatically | ||
| 42 | |||
| 43 | // Initialize OSTIMER with default 1MHz frequency | ||
| 44 | // Adjust this value to match your actual OSTIMER clock frequency | ||
| 45 | hal::ostimer::time_driver::init(hal::config::Config::default().time_interrupt_priority, 1_000_000); | ||
| 46 | |||
| 47 | // Removed force-pend; rely on real hardware match to trigger OS_EVENT. | ||
| 48 | |||
| 49 | // Log using defmt if enabled | ||
| 50 | defmt::info!("OSTIMER async example starting..."); | ||
| 51 | |||
| 52 | loop { | ||
| 53 | defmt::info!("tick"); | ||
| 54 | uart.write_str_blocking("tick\n"); | ||
| 55 | Timer::after(Duration::from_millis(1000)).await; | ||
| 56 | } | ||
| 57 | } | ||
diff --git a/examples/ostimer_counter.rs b/examples/ostimer_counter.rs new file mode 100644 index 000000000..e95140a88 --- /dev/null +++ b/examples/ostimer_counter.rs | |||
| @@ -0,0 +1,128 @@ | |||
| 1 | //! # OSTIMER Counter Reading and Reset Example | ||
| 2 | //! | ||
| 3 | //! This example demonstrates the new timer counter reading and reset functionality | ||
| 4 | //! of the OSTIMER driver. | ||
| 5 | |||
| 6 | #![no_std] | ||
| 7 | #![no_main] | ||
| 8 | |||
| 9 | use embassy_executor::Spawner; | ||
| 10 | use embassy_time::{Duration, Timer}; | ||
| 11 | use hal::bind_interrupts; | ||
| 12 | use {defmt_rtt as _, embassy_mcxa276 as hal, panic_probe as _}; | ||
| 13 | |||
| 14 | mod common; | ||
| 15 | |||
| 16 | bind_interrupts!(struct Irqs { | ||
| 17 | OS_EVENT => hal::ostimer::time_driver::OsEventHandler; | ||
| 18 | }); | ||
| 19 | |||
| 20 | #[embassy_executor::main] | ||
| 21 | async fn main(_spawner: Spawner) { | ||
| 22 | let p = hal::init(Default::default()); | ||
| 23 | |||
| 24 | // Enable/clock OSTIMER0 and UART2 before touching their registers | ||
| 25 | unsafe { | ||
| 26 | common::init_ostimer0(hal::pac()); | ||
| 27 | } | ||
| 28 | unsafe { | ||
| 29 | common::init_uart2(hal::pac()); | ||
| 30 | } | ||
| 31 | let src = unsafe { hal::clocks::uart2_src_hz(hal::pac()) }; | ||
| 32 | let mut uart = hal::uart::Uart::<hal::uart::Lpuart2>::new(p.LPUART2, hal::uart::Config::new(src)); | ||
| 33 | |||
| 34 | uart.write_str_blocking("OSTIMER Counter Reading and Reset Example\n"); | ||
| 35 | |||
| 36 | // Initialize the OSTIMER time driver | ||
| 37 | hal::ostimer::time_driver::init( | ||
| 38 | hal::interrupt::Priority::from(3), | ||
| 39 | 1_000_000, // 1MHz clock | ||
| 40 | ); | ||
| 41 | |||
| 42 | // Create OSTIMER instance | ||
| 43 | let ostimer = hal::ostimer::Ostimer::<hal::ostimer::Ostimer0>::new( | ||
| 44 | p.OSTIMER0, | ||
| 45 | hal::ostimer::Config { | ||
| 46 | init_match_max: true, | ||
| 47 | clock_frequency_hz: 1_000_000, | ||
| 48 | }, | ||
| 49 | hal::pac(), | ||
| 50 | ); | ||
| 51 | |||
| 52 | // Read initial counter value | ||
| 53 | let initial_counter = ostimer.now(); | ||
| 54 | uart.write_str_blocking("Initial counter value: "); | ||
| 55 | write_u64(&mut uart, initial_counter); | ||
| 56 | uart.write_str_blocking("\n"); | ||
| 57 | |||
| 58 | // Wait a bit to let counter increment | ||
| 59 | Timer::after(Duration::from_millis(100)).await; | ||
| 60 | |||
| 61 | // Read counter again | ||
| 62 | let counter_after_wait = ostimer.now(); | ||
| 63 | uart.write_str_blocking("Counter after 100ms wait: "); | ||
| 64 | write_u64(&mut uart, counter_after_wait); | ||
| 65 | uart.write_str_blocking("\n"); | ||
| 66 | uart.write_str_blocking("Difference: "); | ||
| 67 | write_u64(&mut uart, counter_after_wait - initial_counter); | ||
| 68 | uart.write_str_blocking(" ticks\n"); | ||
| 69 | |||
| 70 | // Reset the timer | ||
| 71 | uart.write_str_blocking("Resetting timer...\n"); | ||
| 72 | ostimer.reset(hal::pac()); | ||
| 73 | |||
| 74 | // Read counter after reset | ||
| 75 | let counter_after_reset = ostimer.now(); | ||
| 76 | uart.write_str_blocking("Counter after reset: "); | ||
| 77 | write_u64(&mut uart, counter_after_reset); | ||
| 78 | uart.write_str_blocking("\n"); | ||
| 79 | |||
| 80 | // Wait again to verify timer is working | ||
| 81 | Timer::after(Duration::from_millis(50)).await; | ||
| 82 | |||
| 83 | let final_counter = ostimer.now(); | ||
| 84 | uart.write_str_blocking("Counter after another 50ms: "); | ||
| 85 | write_u64(&mut uart, final_counter); | ||
| 86 | uart.write_str_blocking("\n"); | ||
| 87 | uart.write_str_blocking("Difference after reset: "); | ||
| 88 | write_u64(&mut uart, final_counter - counter_after_reset); | ||
| 89 | uart.write_str_blocking(" ticks\n"); | ||
| 90 | |||
| 91 | uart.write_str_blocking("Example complete\n"); | ||
| 92 | } | ||
| 93 | |||
| 94 | // Helper function to write a u64 value as decimal string | ||
| 95 | fn write_u64(uart: &mut hal::uart::Uart<hal::uart::Lpuart2>, value: u64) { | ||
| 96 | if value == 0 { | ||
| 97 | uart.write_str_blocking("0"); | ||
| 98 | return; | ||
| 99 | } | ||
| 100 | |||
| 101 | let mut buffer = [0u8; 20]; // Enough for max u64 | ||
| 102 | let mut i = 0; | ||
| 103 | let mut v = value; | ||
| 104 | |||
| 105 | while v > 0 { | ||
| 106 | buffer[i] = b'0' + (v % 10) as u8; | ||
| 107 | v /= 10; | ||
| 108 | i += 1; | ||
| 109 | } | ||
| 110 | |||
| 111 | // Write digits in reverse order | ||
| 112 | while i > 0 { | ||
| 113 | i -= 1; | ||
| 114 | match buffer[i] { | ||
| 115 | b'0' => uart.write_str_blocking("0"), | ||
| 116 | b'1' => uart.write_str_blocking("1"), | ||
| 117 | b'2' => uart.write_str_blocking("2"), | ||
| 118 | b'3' => uart.write_str_blocking("3"), | ||
| 119 | b'4' => uart.write_str_blocking("4"), | ||
| 120 | b'5' => uart.write_str_blocking("5"), | ||
| 121 | b'6' => uart.write_str_blocking("6"), | ||
| 122 | b'7' => uart.write_str_blocking("7"), | ||
| 123 | b'8' => uart.write_str_blocking("8"), | ||
| 124 | b'9' => uart.write_str_blocking("9"), | ||
| 125 | _ => uart.write_str_blocking("?"), | ||
| 126 | } | ||
| 127 | } | ||
| 128 | } | ||
diff --git a/examples/ostimer_race_test.rs b/examples/ostimer_race_test.rs new file mode 100644 index 000000000..a637b6353 --- /dev/null +++ b/examples/ostimer_race_test.rs | |||
| @@ -0,0 +1,396 @@ | |||
| 1 | //! # OSTIMER Race Condition Test | ||
| 2 | //! | ||
| 3 | //! This example tests for race conditions in the OSTIMER driver by: | ||
| 4 | //! - Scheduling alarms sequentially (hardware limitation: only one at a time) | ||
| 5 | //! - Reading the counter during interrupt-heavy periods | ||
| 6 | //! - Testing concurrent timer operations | ||
| 7 | //! - Stress testing interrupt handling | ||
| 8 | |||
| 9 | #![no_std] | ||
| 10 | #![no_main] | ||
| 11 | |||
| 12 | use core::sync::atomic::{AtomicU32, Ordering}; | ||
| 13 | |||
| 14 | use embassy_executor::Spawner; | ||
| 15 | use embassy_time::{Duration, Timer}; | ||
| 16 | use hal::bind_interrupts; | ||
| 17 | use {defmt_rtt as _, embassy_mcxa276 as hal, panic_probe as _}; | ||
| 18 | |||
| 19 | mod common; | ||
| 20 | |||
| 21 | bind_interrupts!(struct Irqs { | ||
| 22 | OS_EVENT => hal::ostimer::time_driver::OsEventHandler; | ||
| 23 | }); | ||
| 24 | |||
| 25 | #[used] | ||
| 26 | #[no_mangle] | ||
| 27 | static KEEP_OS_EVENT: unsafe extern "C" fn() = OS_EVENT; | ||
| 28 | |||
| 29 | // Global counters for race condition detection | ||
| 30 | static ALARM_CALLBACK_COUNT: AtomicU32 = AtomicU32::new(0); | ||
| 31 | static INTERRUPT_COUNT: AtomicU32 = AtomicU32::new(0); | ||
| 32 | static RACE_DETECTED: AtomicU32 = AtomicU32::new(0); | ||
| 33 | |||
| 34 | // Alarm callback function | ||
| 35 | fn alarm_callback() { | ||
| 36 | let _count = ALARM_CALLBACK_COUNT.fetch_add(1, Ordering::SeqCst); | ||
| 37 | INTERRUPT_COUNT.fetch_add(1, Ordering::SeqCst); | ||
| 38 | |||
| 39 | // Simulate some work in the callback to increase chance of races | ||
| 40 | for _ in 0..10 { | ||
| 41 | cortex_m::asm::nop(); | ||
| 42 | } | ||
| 43 | } | ||
| 44 | |||
| 45 | fn report_default_handler(uart: &mut hal::uart::Uart<hal::uart::Lpuart2>) { | ||
| 46 | let snapshot = hal::interrupt::default_handler_snapshot(); | ||
| 47 | if snapshot.count == 0 { | ||
| 48 | return; | ||
| 49 | } | ||
| 50 | |||
| 51 | uart.write_str_blocking("WARNING: DefaultHandler executed "); | ||
| 52 | write_u32(uart, snapshot.count); | ||
| 53 | uart.write_str_blocking(" time(s). Vector="); | ||
| 54 | write_u32(uart, snapshot.vector as u32); | ||
| 55 | uart.write_str_blocking(" CFSR=0x"); | ||
| 56 | write_hex32(uart, snapshot.cfsr); | ||
| 57 | uart.write_str_blocking(" HFSR=0x"); | ||
| 58 | write_hex32(uart, snapshot.hfsr); | ||
| 59 | uart.write_str_blocking(" PC=0x"); | ||
| 60 | write_hex32(uart, snapshot.stacked_pc); | ||
| 61 | uart.write_str_blocking(" LR=0x"); | ||
| 62 | write_hex32(uart, snapshot.stacked_lr); | ||
| 63 | uart.write_str_blocking(" SP=0x"); | ||
| 64 | write_hex32(uart, snapshot.stacked_sp); | ||
| 65 | uart.write_str_blocking("\n"); | ||
| 66 | |||
| 67 | hal::interrupt::clear_default_handler_snapshot(); | ||
| 68 | } | ||
| 69 | |||
| 70 | #[embassy_executor::main] | ||
| 71 | async fn main(_spawner: Spawner) { | ||
| 72 | let p = hal::init(Default::default()); | ||
| 73 | |||
| 74 | // Enable/clock OSTIMER0 and UART2 before touching their registers | ||
| 75 | unsafe { | ||
| 76 | common::init_ostimer0(hal::pac()); | ||
| 77 | } | ||
| 78 | unsafe { | ||
| 79 | common::init_uart2(hal::pac()); | ||
| 80 | } | ||
| 81 | let src = unsafe { hal::clocks::uart2_src_hz(hal::pac()) }; | ||
| 82 | let mut uart = hal::uart::Uart::<hal::uart::Lpuart2>::new(p.LPUART2, hal::uart::Config::new(src)); | ||
| 83 | |||
| 84 | uart.write_str_blocking("OSTIMER Race Condition Test Starting...\n"); | ||
| 85 | |||
| 86 | // The bind_interrupts! macro handles handler binding automatically | ||
| 87 | |||
| 88 | // Initialize the OSTIMER time driver FIRST | ||
| 89 | hal::ostimer::time_driver::init( | ||
| 90 | hal::interrupt::Priority::from(3), | ||
| 91 | 1_000_000, // 1MHz clock | ||
| 92 | ); | ||
| 93 | |||
| 94 | uart.write_str_blocking("Time driver initialized\n"); | ||
| 95 | |||
| 96 | // Create OSTIMER instance | ||
| 97 | let ostimer = hal::ostimer::Ostimer::<hal::ostimer::Ostimer0>::new( | ||
| 98 | p.OSTIMER0, | ||
| 99 | hal::ostimer::Config { | ||
| 100 | init_match_max: true, | ||
| 101 | clock_frequency_hz: 1_000_000, | ||
| 102 | }, | ||
| 103 | hal::pac(), | ||
| 104 | ); | ||
| 105 | |||
| 106 | uart.write_str_blocking("OSTIMER instance created\n"); | ||
| 107 | |||
| 108 | // Test 1: Sequential alarm scheduling (OSTIMER only supports one alarm at a time) | ||
| 109 | uart.write_str_blocking("Test 1: Sequential alarm scheduling...\n"); | ||
| 110 | test_rapid_alarms(&ostimer, &mut uart).await; | ||
| 111 | report_default_handler(&mut uart); | ||
| 112 | |||
| 113 | // Test 2: Counter reading during interrupts | ||
| 114 | uart.write_str_blocking("Test 2: Counter reading during interrupts...\n"); | ||
| 115 | test_counter_reading_during_interrupts(&ostimer, &mut uart).await; | ||
| 116 | report_default_handler(&mut uart); | ||
| 117 | |||
| 118 | // Test 3: Concurrent timer operations | ||
| 119 | uart.write_str_blocking("Test 3: Concurrent timer operations...\n"); | ||
| 120 | test_concurrent_operations(&ostimer, &mut uart).await; | ||
| 121 | report_default_handler(&mut uart); | ||
| 122 | |||
| 123 | // Test 4: Timer reset during operation | ||
| 124 | uart.write_str_blocking("Test 4: Timer reset during operation...\n"); | ||
| 125 | test_reset_during_operation(&ostimer, &mut uart, hal::pac()).await; | ||
| 126 | report_default_handler(&mut uart); | ||
| 127 | |||
| 128 | // Report results | ||
| 129 | uart.write_str_blocking("Race condition test complete\n"); | ||
| 130 | uart.write_str_blocking("Callback count: "); | ||
| 131 | write_u32(&mut uart, ALARM_CALLBACK_COUNT.load(Ordering::SeqCst)); | ||
| 132 | uart.write_str_blocking("\nInterrupt count: "); | ||
| 133 | write_u32(&mut uart, INTERRUPT_COUNT.load(Ordering::SeqCst)); | ||
| 134 | uart.write_str_blocking("\nRaces detected: "); | ||
| 135 | write_u32(&mut uart, RACE_DETECTED.load(Ordering::SeqCst)); | ||
| 136 | uart.write_str_blocking("\n"); | ||
| 137 | } | ||
| 138 | |||
| 139 | // Test rapid alarm scheduling to stress interrupt handling | ||
| 140 | async fn test_rapid_alarms( | ||
| 141 | ostimer: &hal::ostimer::Ostimer<'_, hal::ostimer::Ostimer0>, | ||
| 142 | uart: &mut hal::uart::Uart<hal::uart::Lpuart2>, | ||
| 143 | ) { | ||
| 144 | let initial_count = ALARM_CALLBACK_COUNT.load(Ordering::SeqCst); | ||
| 145 | |||
| 146 | // Schedule 10 alarms sequentially (OSTIMER only supports one alarm at a time) | ||
| 147 | for _i in 0..10 { | ||
| 148 | let alarm = hal::ostimer::Alarm::new().with_callback(alarm_callback); | ||
| 149 | let delay_us = 1000; // 1ms delay for each alarm | ||
| 150 | if ostimer.schedule_alarm_delay(&alarm, delay_us) { | ||
| 151 | // Wait for this alarm to complete before scheduling the next | ||
| 152 | Timer::after(Duration::from_micros(delay_us + 100)).await; | ||
| 153 | report_default_handler(uart); | ||
| 154 | } else { | ||
| 155 | RACE_DETECTED.fetch_add(1, Ordering::SeqCst); | ||
| 156 | uart.write_str_blocking("ERROR: Failed to program OSTIMER alarm (match not ready)\n"); | ||
| 157 | } | ||
| 158 | } | ||
| 159 | |||
| 160 | // All alarms should have completed by now | ||
| 161 | let final_count = ALARM_CALLBACK_COUNT.load(Ordering::SeqCst); | ||
| 162 | let expected_count = initial_count + 10; | ||
| 163 | |||
| 164 | if final_count != expected_count { | ||
| 165 | RACE_DETECTED.fetch_add(1, Ordering::SeqCst); | ||
| 166 | uart.write_str_blocking("ERROR: Expected "); | ||
| 167 | write_u32(uart, expected_count); | ||
| 168 | uart.write_str_blocking(" callbacks, got "); | ||
| 169 | write_u32(uart, final_count); | ||
| 170 | uart.write_str_blocking("\n"); | ||
| 171 | } else { | ||
| 172 | uart.write_str_blocking("PASS: All rapid alarms executed\n"); | ||
| 173 | } | ||
| 174 | } | ||
| 175 | |||
| 176 | // Test reading counter while interrupts are firing | ||
| 177 | async fn test_counter_reading_during_interrupts( | ||
| 178 | ostimer: &hal::ostimer::Ostimer<'_, hal::ostimer::Ostimer0>, | ||
| 179 | uart: &mut hal::uart::Uart<hal::uart::Lpuart2>, | ||
| 180 | ) { | ||
| 181 | let initial_interrupt_count = INTERRUPT_COUNT.load(Ordering::SeqCst); | ||
| 182 | |||
| 183 | // Schedule an alarm that will fire soon | ||
| 184 | let alarm = hal::ostimer::Alarm::new().with_callback(alarm_callback); | ||
| 185 | if !ostimer.schedule_alarm_delay(&alarm, 500) { | ||
| 186 | RACE_DETECTED.fetch_add(1, Ordering::SeqCst); | ||
| 187 | uart.write_str_blocking("ERROR: Failed to program OSTIMER alarm before counter stress\n"); | ||
| 188 | } | ||
| 189 | |||
| 190 | // While alarm is pending, read the counter many times rapidly | ||
| 191 | // This tests if counter reading is atomic and doesn't get corrupted by interrupts | ||
| 192 | let mut last_counter = ostimer.now(); | ||
| 193 | let mut consistent_reads = 0; | ||
| 194 | let mut total_reads = 0; | ||
| 195 | |||
| 196 | for _ in 0..1000 { | ||
| 197 | let current_counter = ostimer.now(); | ||
| 198 | total_reads += 1; | ||
| 199 | |||
| 200 | // Check if counter is monotonically increasing (basic sanity check) | ||
| 201 | if current_counter >= last_counter { | ||
| 202 | consistent_reads += 1; | ||
| 203 | } | ||
| 204 | last_counter = current_counter; | ||
| 205 | |||
| 206 | // Small delay between reads | ||
| 207 | for _ in 0..10 { | ||
| 208 | cortex_m::asm::nop(); | ||
| 209 | } | ||
| 210 | |||
| 211 | report_default_handler(uart); | ||
| 212 | } | ||
| 213 | |||
| 214 | // Wait for alarm to complete | ||
| 215 | Timer::after(Duration::from_millis(1)).await; | ||
| 216 | |||
| 217 | let final_interrupt_count = INTERRUPT_COUNT.load(Ordering::SeqCst); | ||
| 218 | |||
| 219 | if consistent_reads == total_reads { | ||
| 220 | uart.write_str_blocking("PASS: Counter reading consistent during interrupts\n"); | ||
| 221 | } else { | ||
| 222 | RACE_DETECTED.fetch_add(1, Ordering::SeqCst); | ||
| 223 | uart.write_str_blocking("ERROR: Counter reading inconsistent: "); | ||
| 224 | write_u32(uart, consistent_reads); | ||
| 225 | uart.write_str_blocking("/"); | ||
| 226 | write_u32(uart, total_reads); | ||
| 227 | uart.write_str_blocking(" consistent\n"); | ||
| 228 | } | ||
| 229 | |||
| 230 | if final_interrupt_count > initial_interrupt_count { | ||
| 231 | uart.write_str_blocking("PASS: Interrupt fired during counter reading test\n"); | ||
| 232 | } else { | ||
| 233 | uart.write_str_blocking("WARNING: No interrupt fired during counter reading test\n"); | ||
| 234 | } | ||
| 235 | } | ||
| 236 | |||
| 237 | // Test concurrent timer operations (embassy-time + alarms) | ||
| 238 | async fn test_concurrent_operations( | ||
| 239 | ostimer: &hal::ostimer::Ostimer<'_, hal::ostimer::Ostimer0>, | ||
| 240 | uart: &mut hal::uart::Uart<hal::uart::Lpuart2>, | ||
| 241 | ) { | ||
| 242 | let initial_interrupt_count = INTERRUPT_COUNT.load(Ordering::SeqCst); | ||
| 243 | |||
| 244 | // Start an embassy-time timer | ||
| 245 | let timer_future = Timer::after(Duration::from_millis(2)); | ||
| 246 | |||
| 247 | // Schedule an alarm that should fire before the timer | ||
| 248 | let alarm = hal::ostimer::Alarm::new().with_callback(alarm_callback); | ||
| 249 | if !ostimer.schedule_alarm_delay(&alarm, 1000) { | ||
| 250 | RACE_DETECTED.fetch_add(1, Ordering::SeqCst); | ||
| 251 | uart.write_str_blocking("ERROR: Failed to program OSTIMER alarm before concurrent operations\n"); | ||
| 252 | } | ||
| 253 | |||
| 254 | // Wait for both to complete | ||
| 255 | timer_future.await; | ||
| 256 | |||
| 257 | let final_interrupt_count = INTERRUPT_COUNT.load(Ordering::SeqCst); | ||
| 258 | |||
| 259 | if final_interrupt_count > initial_interrupt_count { | ||
| 260 | uart.write_str_blocking("PASS: Concurrent operations completed\n"); | ||
| 261 | } else { | ||
| 262 | uart.write_str_blocking("WARNING: No interrupts during concurrent operations\n"); | ||
| 263 | } | ||
| 264 | } | ||
| 265 | |||
| 266 | // Test timer reset during active operations | ||
| 267 | async fn test_reset_during_operation( | ||
| 268 | ostimer: &hal::ostimer::Ostimer<'_, hal::ostimer::Ostimer0>, | ||
| 269 | uart: &mut hal::uart::Uart<hal::uart::Lpuart2>, | ||
| 270 | peripherals: &hal::pac::Peripherals, | ||
| 271 | ) { | ||
| 272 | let initial_counter = ostimer.now(); | ||
| 273 | |||
| 274 | // Schedule an alarm | ||
| 275 | let alarm = hal::ostimer::Alarm::new().with_callback(alarm_callback); | ||
| 276 | if !ostimer.schedule_alarm_delay(&alarm, 2000) { | ||
| 277 | RACE_DETECTED.fetch_add(1, Ordering::SeqCst); | ||
| 278 | uart.write_str_blocking("ERROR: Failed to program OSTIMER alarm before reset test\n"); | ||
| 279 | } | ||
| 280 | |||
| 281 | // Wait a bit then reset the timer | ||
| 282 | Timer::after(Duration::from_millis(1)).await; | ||
| 283 | ostimer.reset(peripherals); | ||
| 284 | |||
| 285 | // Check counter after reset | ||
| 286 | let counter_after_reset = ostimer.now(); | ||
| 287 | |||
| 288 | // Wait to see if the alarm still fires (it shouldn't after reset) | ||
| 289 | Timer::after(Duration::from_millis(2)).await; | ||
| 290 | |||
| 291 | let final_counter = ostimer.now(); | ||
| 292 | |||
| 293 | if counter_after_reset < initial_counter { | ||
| 294 | uart.write_str_blocking("PASS: Timer reset successful\n"); | ||
| 295 | } else { | ||
| 296 | RACE_DETECTED.fetch_add(1, Ordering::SeqCst); | ||
| 297 | uart.write_str_blocking("ERROR: Timer reset may have failed\n"); | ||
| 298 | } | ||
| 299 | |||
| 300 | uart.write_str_blocking("Counter progression after reset: "); | ||
| 301 | write_u64(uart, initial_counter); | ||
| 302 | uart.write_str_blocking(" -> "); | ||
| 303 | write_u64(uart, counter_after_reset); | ||
| 304 | uart.write_str_blocking(" -> "); | ||
| 305 | write_u64(uart, final_counter); | ||
| 306 | uart.write_str_blocking("\n"); | ||
| 307 | } | ||
| 308 | |||
| 309 | // Helper function to write a u32 value as decimal string | ||
| 310 | fn write_u32(uart: &mut hal::uart::Uart<hal::uart::Lpuart2>, value: u32) { | ||
| 311 | if value == 0 { | ||
| 312 | uart.write_str_blocking("0"); | ||
| 313 | return; | ||
| 314 | } | ||
| 315 | |||
| 316 | let mut buffer = [0u8; 10]; // Enough for max u32 | ||
| 317 | let mut i = 0; | ||
| 318 | let mut v = value; | ||
| 319 | |||
| 320 | while v > 0 { | ||
| 321 | buffer[i] = b'0' + (v % 10) as u8; | ||
| 322 | v /= 10; | ||
| 323 | i += 1; | ||
| 324 | } | ||
| 325 | |||
| 326 | // Write digits in reverse order | ||
| 327 | while i > 0 { | ||
| 328 | i -= 1; | ||
| 329 | match buffer[i] { | ||
| 330 | b'0' => uart.write_str_blocking("0"), | ||
| 331 | b'1' => uart.write_str_blocking("1"), | ||
| 332 | b'2' => uart.write_str_blocking("2"), | ||
| 333 | b'3' => uart.write_str_blocking("3"), | ||
| 334 | b'4' => uart.write_str_blocking("4"), | ||
| 335 | b'5' => uart.write_str_blocking("5"), | ||
| 336 | b'6' => uart.write_str_blocking("6"), | ||
| 337 | b'7' => uart.write_str_blocking("7"), | ||
| 338 | b'8' => uart.write_str_blocking("8"), | ||
| 339 | b'9' => uart.write_str_blocking("9"), | ||
| 340 | _ => uart.write_str_blocking("?"), | ||
| 341 | } | ||
| 342 | } | ||
| 343 | } | ||
| 344 | |||
| 345 | fn write_hex32(uart: &mut hal::uart::Uart<hal::uart::Lpuart2>, value: u32) { | ||
| 346 | let mut buf = [b'0'; 8]; | ||
| 347 | let mut tmp = value; | ||
| 348 | for i in (0..8).rev() { | ||
| 349 | let digit = (tmp & 0xF) as u8; | ||
| 350 | buf[i] = match digit { | ||
| 351 | 0..=9 => b'0' + digit, | ||
| 352 | 10..=15 => b'A' + (digit - 10), | ||
| 353 | _ => b'?', | ||
| 354 | }; | ||
| 355 | tmp >>= 4; | ||
| 356 | } | ||
| 357 | for b in &buf { | ||
| 358 | uart.write_byte(*b); | ||
| 359 | } | ||
| 360 | } | ||
| 361 | |||
| 362 | // Helper function to write a u64 value as decimal string | ||
| 363 | fn write_u64(uart: &mut hal::uart::Uart<hal::uart::Lpuart2>, value: u64) { | ||
| 364 | if value == 0 { | ||
| 365 | uart.write_str_blocking("0"); | ||
| 366 | return; | ||
| 367 | } | ||
| 368 | |||
| 369 | let mut buffer = [0u8; 20]; // Enough for max u64 | ||
| 370 | let mut i = 0; | ||
| 371 | let mut v = value; | ||
| 372 | |||
| 373 | while v > 0 { | ||
| 374 | buffer[i] = b'0' + (v % 10) as u8; | ||
| 375 | v /= 10; | ||
| 376 | i += 1; | ||
| 377 | } | ||
| 378 | |||
| 379 | // Write digits in reverse order | ||
| 380 | while i > 0 { | ||
| 381 | i -= 1; | ||
| 382 | match buffer[i] { | ||
| 383 | b'0' => uart.write_str_blocking("0"), | ||
| 384 | b'1' => uart.write_str_blocking("1"), | ||
| 385 | b'2' => uart.write_str_blocking("2"), | ||
| 386 | b'3' => uart.write_str_blocking("3"), | ||
| 387 | b'4' => uart.write_str_blocking("4"), | ||
| 388 | b'5' => uart.write_str_blocking("5"), | ||
| 389 | b'6' => uart.write_str_blocking("6"), | ||
| 390 | b'7' => uart.write_str_blocking("7"), | ||
| 391 | b'8' => uart.write_str_blocking("8"), | ||
| 392 | b'9' => uart.write_str_blocking("9"), | ||
| 393 | _ => uart.write_str_blocking("?"), | ||
| 394 | } | ||
| 395 | } | ||
| 396 | } | ||
diff --git a/examples/rtc_alarm.rs b/examples/rtc_alarm.rs new file mode 100644 index 000000000..c27fd4c55 --- /dev/null +++ b/examples/rtc_alarm.rs | |||
| @@ -0,0 +1,87 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | |||
| 4 | use embassy_executor::Spawner; | ||
| 5 | use hal::rtc::{RtcDateTime, RtcInterruptEnable}; | ||
| 6 | use hal::{uart, InterruptExt}; | ||
| 7 | use {cortex_m, embassy_mcxa276 as hal}; | ||
| 8 | |||
| 9 | mod common; | ||
| 10 | |||
| 11 | type MyRtc = hal::rtc::Rtc<hal::rtc::Rtc0>; | ||
| 12 | |||
| 13 | use embassy_mcxa276::bind_interrupts; | ||
| 14 | use {defmt_rtt as _, panic_probe as _}; | ||
| 15 | |||
| 16 | bind_interrupts!(struct Irqs { | ||
| 17 | RTC => hal::rtc::RtcHandler; | ||
| 18 | }); | ||
| 19 | |||
| 20 | #[used] | ||
| 21 | #[no_mangle] | ||
| 22 | static KEEP_RTC: unsafe extern "C" fn() = RTC; | ||
| 23 | |||
| 24 | #[embassy_executor::main] | ||
| 25 | async fn main(_spawner: Spawner) { | ||
| 26 | let p = hal::init(hal::config::Config::default()); | ||
| 27 | |||
| 28 | unsafe { | ||
| 29 | common::init_uart2(hal::pac()); | ||
| 30 | } | ||
| 31 | |||
| 32 | let src = unsafe { hal::clocks::uart2_src_hz(hal::pac()) }; | ||
| 33 | let uart = uart::Uart::<uart::Lpuart2>::new(p.LPUART2, uart::Config::new(src)); | ||
| 34 | |||
| 35 | uart.write_str_blocking("\r\n=== RTC Alarm Example ===\r\n"); | ||
| 36 | |||
| 37 | unsafe { hal::clocks::init_fro16k(hal::pac()) }; | ||
| 38 | |||
| 39 | let rtc_config = hal::rtc::get_default_config(); | ||
| 40 | |||
| 41 | let rtc = MyRtc::new(p.RTC0, rtc_config); | ||
| 42 | |||
| 43 | let now = RtcDateTime { | ||
| 44 | year: 2025, | ||
| 45 | month: 10, | ||
| 46 | day: 15, | ||
| 47 | hour: 14, | ||
| 48 | minute: 30, | ||
| 49 | second: 0, | ||
| 50 | }; | ||
| 51 | |||
| 52 | rtc.stop(); | ||
| 53 | |||
| 54 | uart.write_str_blocking("Time set to: 2025-10-15 14:30:00\r\n"); | ||
| 55 | rtc.set_datetime(now); | ||
| 56 | |||
| 57 | let mut alarm = now; | ||
| 58 | alarm.second += 10; | ||
| 59 | |||
| 60 | rtc.set_alarm(alarm); | ||
| 61 | uart.write_str_blocking("Alarm set for: 2025-10-15 14:30:10 (+10 seconds)\r\n"); | ||
| 62 | |||
| 63 | rtc.set_interrupt(RtcInterruptEnable::RTC_ALARM_INTERRUPT_ENABLE); | ||
| 64 | |||
| 65 | unsafe { | ||
| 66 | hal::interrupt::RTC.enable(); | ||
| 67 | } | ||
| 68 | |||
| 69 | unsafe { | ||
| 70 | cortex_m::interrupt::enable(); | ||
| 71 | } | ||
| 72 | |||
| 73 | rtc.start(); | ||
| 74 | |||
| 75 | uart.write_str_blocking("RTC started, waiting for alarm...\r\n"); | ||
| 76 | |||
| 77 | loop { | ||
| 78 | if rtc.is_alarm_triggered() { | ||
| 79 | uart.write_str_blocking("\r\n*** ALARM TRIGGERED! ***\r\n"); | ||
| 80 | break; | ||
| 81 | } | ||
| 82 | } | ||
| 83 | |||
| 84 | uart.write_str_blocking("Example complete - Test PASSED!\r\n"); | ||
| 85 | |||
| 86 | loop {} | ||
| 87 | } | ||
diff --git a/examples/uart_interrupt.rs b/examples/uart_interrupt.rs new file mode 100644 index 000000000..bd734f859 --- /dev/null +++ b/examples/uart_interrupt.rs | |||
| @@ -0,0 +1,69 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | |||
| 4 | use embassy_executor::Spawner; | ||
| 5 | use embassy_mcxa276 as hal; | ||
| 6 | use hal::interrupt::typelevel::Handler; | ||
| 7 | use hal::uart; | ||
| 8 | |||
| 9 | mod common; | ||
| 10 | |||
| 11 | use embassy_mcxa276::bind_interrupts; | ||
| 12 | use {defmt_rtt as _, panic_probe as _}; | ||
| 13 | |||
| 14 | // Bind LPUART2 interrupt to our handler | ||
| 15 | bind_interrupts!(struct Irqs { | ||
| 16 | LPUART2 => hal::uart::UartInterruptHandler; | ||
| 17 | }); | ||
| 18 | |||
| 19 | #[used] | ||
| 20 | #[no_mangle] | ||
| 21 | static KEEP_LPUART2: unsafe extern "C" fn() = LPUART2; | ||
| 22 | |||
| 23 | // Wrapper function for the interrupt handler | ||
| 24 | unsafe extern "C" fn lpuart2_handler() { | ||
| 25 | hal::uart::UartInterruptHandler::on_interrupt(); | ||
| 26 | } | ||
| 27 | |||
| 28 | #[embassy_executor::main] | ||
| 29 | async fn main(_spawner: Spawner) { | ||
| 30 | let _p = hal::init(hal::config::Config::default()); | ||
| 31 | |||
| 32 | // Enable/clock UART2 before touching its registers | ||
| 33 | unsafe { | ||
| 34 | common::init_uart2(hal::pac()); | ||
| 35 | } | ||
| 36 | let src = unsafe { hal::clocks::uart2_src_hz(hal::pac()) }; | ||
| 37 | let uart = uart::Uart::<uart::Lpuart2>::new(_p.LPUART2, uart::Config::new(src)); | ||
| 38 | |||
| 39 | // Configure LPUART2 interrupt for UART operation BEFORE any UART usage | ||
| 40 | hal::interrupt::LPUART2.configure_for_uart(hal::interrupt::Priority::from(3)); | ||
| 41 | |||
| 42 | // Manually install the interrupt handler and enable RX IRQs in the peripheral | ||
| 43 | unsafe { | ||
| 44 | hal::interrupt::LPUART2.install_handler(lpuart2_handler); | ||
| 45 | // Enable RX interrupts so the handler actually fires on incoming bytes | ||
| 46 | uart.enable_rx_interrupts(); | ||
| 47 | } | ||
| 48 | |||
| 49 | // Print welcome message | ||
| 50 | uart.write_str_blocking("UART interrupt echo demo starting...\r\n"); | ||
| 51 | uart.write_str_blocking("Type characters to echo them back.\r\n"); | ||
| 52 | |||
| 53 | // Log using defmt if enabled | ||
| 54 | defmt::info!("UART interrupt echo demo starting..."); | ||
| 55 | |||
| 56 | loop { | ||
| 57 | // Check if we have received any data | ||
| 58 | if uart.rx_data_available() { | ||
| 59 | if let Some(byte) = uart.try_read_byte() { | ||
| 60 | // Echo it back | ||
| 61 | uart.write_byte(byte); | ||
| 62 | uart.write_str_blocking(" (received)\r\n"); | ||
| 63 | } | ||
| 64 | } else { | ||
| 65 | // No data available, wait a bit before checking again | ||
| 66 | cortex_m::asm::delay(12_000_000); // ~1 second at 12MHz | ||
| 67 | } | ||
| 68 | } | ||
| 69 | } | ||
diff --git a/memory.x b/memory.x new file mode 100644 index 000000000..b47534f18 --- /dev/null +++ b/memory.x | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | /* Memory layout for MCXA276 - RAM execution with cortex-m-rt */ | ||
| 2 | MEMORY | ||
| 3 | { | ||
| 4 | /* FLASH and RAM overlap for RAM-execution experiments. */ | ||
| 5 | FLASH : ORIGIN = 0x20000000, LENGTH = 128K | ||
| 6 | /* RAM overlaps FLASH */ | ||
| 7 | RAM : ORIGIN = 0x20000000, LENGTH = 128K | ||
| 8 | } | ||
| 9 | |||
| 10 | /* Leave symbol and section boundary definitions to cortex-m-rt's link.x. */ | ||
| @@ -0,0 +1,109 @@ | |||
| 1 | /* Simple RAM execution linker script for MCXA276 */ | ||
| 2 | MEMORY | ||
| 3 | { | ||
| 4 | RAM : ORIGIN = 0x20000000, LENGTH = 128K | ||
| 5 | } | ||
| 6 | |||
| 7 | /* Pull in device default interrupt symbol aliases (e.g., CMC = DefaultHandler) */ | ||
| 8 | INCLUDE device.x | ||
| 9 | |||
| 10 | |||
| 11 | /* Provide core exception weak aliases if not supplied by cortex-m-rt's link.x */ | ||
| 12 | PROVIDE(NonMaskableInt = DefaultHandler); | ||
| 13 | PROVIDE(HardFault = DefaultHandler); | ||
| 14 | PROVIDE(MemoryManagement = DefaultHandler); | ||
| 15 | PROVIDE(BusFault = DefaultHandler); | ||
| 16 | PROVIDE(UsageFault = DefaultHandler); | ||
| 17 | PROVIDE(SecureFault = DefaultHandler); | ||
| 18 | PROVIDE(SVCall = DefaultHandler); | ||
| 19 | PROVIDE(DebugMonitor = DefaultHandler); | ||
| 20 | PROVIDE(PendSV = DefaultHandler); | ||
| 21 | PROVIDE(SysTick = DefaultHandler); | ||
| 22 | |||
| 23 | /* In RAM-run we have no FLASH sidata; copy from sdata */ | ||
| 24 | __sidata = __sdata; | ||
| 25 | |||
| 26 | /* Ensure the PAC interrupt table is kept */ | ||
| 27 | EXTERN(__INTERRUPTS); | ||
| 28 | |||
| 29 | |||
| 30 | /* Pull in defmt's linker script to generate the defmt table that host decoders expect */ | ||
| 31 | INCLUDE defmt.x | ||
| 32 | |||
| 33 | ENTRY(Reset) | ||
| 34 | EXTERN(VECTOR_TABLE) | ||
| 35 | EXTERN(Reset) | ||
| 36 | EXTERN(main) | ||
| 37 | |||
| 38 | /* Define _stack_start at end of RAM BEFORE it's used in vector table */ | ||
| 39 | _stack_start = ORIGIN(RAM) + LENGTH(RAM); | ||
| 40 | |||
| 41 | SECTIONS | ||
| 42 | { | ||
| 43 | .vector_table ORIGIN(RAM) : | ||
| 44 | { | ||
| 45 | /* Slot 0: Initial stack pointer - use our explicitly set _stack_start */ | ||
| 46 | LONG(_stack_start); | ||
| 47 | /* Slot 1: Reset vector - address of Reset function with Thumb bit set */ | ||
| 48 | LONG(Reset | 1); | ||
| 49 | /* Cortex-M33 core exceptions (slots 2-14) */ | ||
| 50 | KEEP(*(.vector_table.exceptions)); | ||
| 51 | /* Peripheral interrupt vectors provided by PAC (slots 16+) */ | ||
| 52 | KEEP(*(.vector_table.interrupts)); | ||
| 53 | } > RAM | ||
| 54 | |||
| 55 | .text : | ||
| 56 | { | ||
| 57 | KEEP(*(.text.Reset)); | ||
| 58 | KEEP(*(.text.main)); | ||
| 59 | *(.text .text.*); | ||
| 60 | } > RAM | ||
| 61 | |||
| 62 | /* Keep defmt table and fragments so host decoders can find metadata */ | ||
| 63 | .defmt : | ||
| 64 | { | ||
| 65 | KEEP(*(.defmt)); | ||
| 66 | KEEP(*(.defmt.*)); | ||
| 67 | } > RAM | ||
| 68 | |||
| 69 | .rodata : | ||
| 70 | { | ||
| 71 | *(.rodata .rodata.*); | ||
| 72 | } > RAM | ||
| 73 | |||
| 74 | .data : | ||
| 75 | { | ||
| 76 | . = ALIGN(4); | ||
| 77 | __sdata = .; | ||
| 78 | *(.data .data.*); | ||
| 79 | . = ALIGN(4); | ||
| 80 | __edata = .; | ||
| 81 | } > RAM | ||
| 82 | |||
| 83 | /* Ensure RTT control block with "SEGGER RTT" signature is loaded to RAM */ | ||
| 84 | .rtt : | ||
| 85 | { | ||
| 86 | KEEP(*(.rtt)); | ||
| 87 | } > RAM | ||
| 88 | |||
| 89 | /* Place uninitialized buffers (like defmt-rtt) in RAM; load is fine for RAM-run */ | ||
| 90 | .uninit : | ||
| 91 | { | ||
| 92 | *(.uninit .uninit.*); | ||
| 93 | } > RAM | ||
| 94 | |||
| 95 | .bss : | ||
| 96 | { | ||
| 97 | . = ALIGN(4); | ||
| 98 | __sbss = .; | ||
| 99 | *(.bss .bss.*); | ||
| 100 | . = ALIGN(4); | ||
| 101 | __ebss = .; | ||
| 102 | } > RAM | ||
| 103 | |||
| 104 | /* Discard exception unwinding info */ | ||
| 105 | /DISCARD/ : | ||
| 106 | { | ||
| 107 | *(.ARM.exidx .ARM.exidx.*); | ||
| 108 | } | ||
| 109 | } | ||
| @@ -0,0 +1,93 @@ | |||
| 1 | #!/usr/bin/env bash | ||
| 2 | set -euo pipefail | ||
| 3 | |||
| 4 | ELF="${1:-}" | ||
| 5 | if [[ -z "${ELF}" ]]; then | ||
| 6 | echo "Usage: $0 <elf_file>" | ||
| 7 | exit 1 | ||
| 8 | fi | ||
| 9 | if [[ ! -f "${ELF}" ]]; then | ||
| 10 | echo "ELF not found: ${ELF}" | ||
| 11 | exit 1 | ||
| 12 | fi | ||
| 13 | |||
| 14 | # Configurable via env | ||
| 15 | CHIP="${CHIP:-MCXA276}" | ||
| 16 | SPEED="${PROBE_SPEED:-1000}" # kHz | ||
| 17 | # Default to J-Link if PROBE not provided | ||
| 18 | PROBE_OPT=(--probe "${PROBE:-1366:0101:000600110607}") | ||
| 19 | PORT="${PROBE_RS_GDB_PORT:-1337}" | ||
| 20 | |||
| 21 | cleanup() { | ||
| 22 | if [[ -n "${GDB_SERVER_PID:-}" ]]; then kill "${GDB_SERVER_PID}" 2>/dev/null || true; fi | ||
| 23 | [[ -n "${GDB_SCRIPT:-}" ]] && rm -f "${GDB_SCRIPT}" || true | ||
| 24 | [[ -n "${SERVER_LOG:-}" ]] && rm -f "${SERVER_LOG}" || true | ||
| 25 | } | ||
| 26 | trap cleanup EXIT | ||
| 27 | |||
| 28 | if ! command -v probe-rs >/dev/null 2>&1; then | ||
| 29 | echo "probe-rs not found (cargo install probe-rs --features cli)" | ||
| 30 | exit 1 | ||
| 31 | fi | ||
| 32 | if ! command -v gdb-multiarch >/dev/null 2>&1; then | ||
| 33 | echo "gdb-multiarch not found; install it (e.g., sudo apt install gdb-multiarch)." | ||
| 34 | exit 1 | ||
| 35 | fi | ||
| 36 | |||
| 37 | # Start probe-rs GDB server and capture its output to a log (do not hide errors) | ||
| 38 | SERVER_LOG=$(mktemp) | ||
| 39 | set +e | ||
| 40 | probe-rs gdb --chip "${CHIP}" --protocol swd --speed "${SPEED}" --non-interactive "${ELF}" "${PROBE_OPT[@]}" \ | ||
| 41 | >"${SERVER_LOG}" 2>&1 & | ||
| 42 | GDB_SERVER_PID=$! | ||
| 43 | set -e | ||
| 44 | |||
| 45 | # Wait for server readiness without touching the TCP port to avoid corrupting the GDB protocol | ||
| 46 | ready="" | ||
| 47 | for _ in {1..50}; do | ||
| 48 | if grep -q "Firing up GDB stub" "${SERVER_LOG}"; then ready=1; break; fi | ||
| 49 | if grep -q "Connecting to the chip was unsuccessful" "${SERVER_LOG}"; then | ||
| 50 | echo "probe-rs gdb server failed to connect to target. Log:" >&2 | ||
| 51 | echo "----- probe-rs gdb log -----" >&2 | ||
| 52 | sed -e 's/^/ /' "${SERVER_LOG}" >&2 || true | ||
| 53 | exit 1 | ||
| 54 | fi | ||
| 55 | sleep 0.1 | ||
| 56 | done | ||
| 57 | if [[ -z "${ready}" ]]; then | ||
| 58 | echo "probe-rs gdb server did not report readiness. Log:" >&2 | ||
| 59 | echo "----- probe-rs gdb log -----" >&2 | ||
| 60 | sed -e 's/^/ /' "${SERVER_LOG}" >&2 || true | ||
| 61 | exit 1 | ||
| 62 | fi | ||
| 63 | |||
| 64 | # GDB script: load to RAM and run, no reset | ||
| 65 | GDB_SCRIPT=$(mktemp) | ||
| 66 | cat >"${GDB_SCRIPT}" <<EOF | ||
| 67 | set pagination off | ||
| 68 | set confirm off | ||
| 69 | set mem inaccessible-by-default off | ||
| 70 | |||
| 71 | # Connect and load without reset | ||
| 72 | target remote :${PORT} | ||
| 73 | monitor halt | ||
| 74 | load | ||
| 75 | # Set VTOR to point to our RAM vector table at 0x20000000 | ||
| 76 | # This ensures the CPU uses the correct initial SP and Reset vector | ||
| 77 | set *0xE000ED08 = 0x20000000 | ||
| 78 | # Now read SP and PC from our vector table and set them | ||
| 79 | set \$sp = *(unsigned int*)0x20000000 | ||
| 80 | set \$pc = *(unsigned int*)0x20000004 | ||
| 81 | # Run target (blocks here until you Ctrl+C, like before) | ||
| 82 | continue | ||
| 83 | EOF | ||
| 84 | |||
| 85 | # Run gdb against the server | ||
| 86 | if ! gdb-multiarch -q -batch -x "${GDB_SCRIPT}" "${ELF}"; then | ||
| 87 | echo "GDB failed to load/run. probe-rs gdb server log:" >&2 | ||
| 88 | echo "----- probe-rs gdb log -----" >&2 | ||
| 89 | sed -e 's/^/ /' "${SERVER_LOG}" >&2 || true | ||
| 90 | exit 1 | ||
| 91 | fi | ||
| 92 | |||
| 93 | echo "Program loaded and started (no reset)" | ||
diff --git a/rust-toolchain.toml b/rust-toolchain.toml deleted file mode 100644 index 367cc1fc3..000000000 --- a/rust-toolchain.toml +++ /dev/null | |||
| @@ -1,2 +0,0 @@ | |||
| 1 | [toolchain] | ||
| 2 | components = ["rustfmt", "clippy"] | ||
diff --git a/rustfmt.toml b/rustfmt.toml index 753065179..9eb3c3b4f 100644 --- a/rustfmt.toml +++ b/rustfmt.toml | |||
| @@ -1 +1,3 @@ | |||
| 1 | group_imports = "StdExternalCrate" | ||
| 2 | imports_granularity = "Module" | ||
| 1 | max_width = 120 | 3 | max_width = 120 |
diff --git a/src/adc.rs b/src/adc.rs new file mode 100644 index 000000000..d456971f7 --- /dev/null +++ b/src/adc.rs | |||
| @@ -0,0 +1,388 @@ | |||
| 1 | //! ADC driver | ||
| 2 | use core::sync::atomic::{AtomicBool, Ordering}; | ||
| 3 | |||
| 4 | use crate::pac; | ||
| 5 | use crate::pac::adc1::cfg::{HptExdi, Pwrsel, Refsel, Tcmdres, Tprictrl, Tres}; | ||
| 6 | use crate::pac::adc1::cmdh1::{Avgs, Cmpen, Next, Sts}; | ||
| 7 | use crate::pac::adc1::cmdl1::{Adch, Ctype, Mode}; | ||
| 8 | use crate::pac::adc1::ctrl::CalAvgs; | ||
| 9 | use crate::pac::adc1::tctrl::{Tcmd, Tpri}; | ||
| 10 | |||
| 11 | type Regs = pac::adc1::RegisterBlock; | ||
| 12 | |||
| 13 | static INTERRUPT_TRIGGERED: AtomicBool = AtomicBool::new(false); | ||
| 14 | // Token-based instance pattern like embassy-imxrt | ||
| 15 | pub trait Instance { | ||
| 16 | fn ptr() -> *const Regs; | ||
| 17 | } | ||
| 18 | |||
| 19 | /// Token for ADC1 | ||
| 20 | pub type Adc1 = crate::peripherals::ADC1; | ||
| 21 | impl Instance for crate::peripherals::ADC1 { | ||
| 22 | #[inline(always)] | ||
| 23 | fn ptr() -> *const Regs { | ||
| 24 | pac::Adc1::ptr() | ||
| 25 | } | ||
| 26 | } | ||
| 27 | |||
| 28 | // Also implement Instance for the Peri wrapper type | ||
| 29 | impl Instance for embassy_hal_internal::Peri<'_, crate::peripherals::ADC1> { | ||
| 30 | #[inline(always)] | ||
| 31 | fn ptr() -> *const Regs { | ||
| 32 | pac::Adc1::ptr() | ||
| 33 | } | ||
| 34 | } | ||
| 35 | |||
| 36 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
| 37 | #[repr(u8)] | ||
| 38 | pub enum TriggerPriorityPolicy { | ||
| 39 | ConvPreemptImmediatelyNotAutoResumed = 0, | ||
| 40 | ConvPreemptSoftlyNotAutoResumed = 1, | ||
| 41 | ConvPreemptImmediatelyAutoRestarted = 4, | ||
| 42 | ConvPreemptSoftlyAutoRestarted = 5, | ||
| 43 | ConvPreemptImmediatelyAutoResumed = 12, | ||
| 44 | ConvPreemptSoftlyAutoResumed = 13, | ||
| 45 | ConvPreemptSubsequentlyNotAutoResumed = 2, | ||
| 46 | ConvPreemptSubsequentlyAutoRestarted = 6, | ||
| 47 | ConvPreemptSubsequentlyAutoResumed = 14, | ||
| 48 | TriggerPriorityExceptionDisabled = 16, | ||
| 49 | } | ||
| 50 | |||
| 51 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
| 52 | pub struct LpadcConfig { | ||
| 53 | pub enable_in_doze_mode: bool, | ||
| 54 | pub conversion_average_mode: CalAvgs, | ||
| 55 | pub enable_analog_preliminary: bool, | ||
| 56 | pub power_up_delay: u8, | ||
| 57 | pub reference_voltage_source: Refsel, | ||
| 58 | pub power_level_mode: Pwrsel, | ||
| 59 | pub trigger_priority_policy: TriggerPriorityPolicy, | ||
| 60 | pub enable_conv_pause: bool, | ||
| 61 | pub conv_pause_delay: u16, | ||
| 62 | pub fifo_watermark: u8, | ||
| 63 | } | ||
| 64 | |||
| 65 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
| 66 | pub struct ConvCommandConfig { | ||
| 67 | pub sample_channel_mode: Ctype, | ||
| 68 | pub channel_number: Adch, | ||
| 69 | pub chained_next_command_number: Next, | ||
| 70 | pub enable_auto_channel_increment: bool, | ||
| 71 | pub loop_count: u8, | ||
| 72 | pub hardware_average_mode: Avgs, | ||
| 73 | pub sample_time_mode: Sts, | ||
| 74 | pub hardware_compare_mode: Cmpen, | ||
| 75 | pub hardware_compare_value_high: u32, | ||
| 76 | pub hardware_compare_value_low: u32, | ||
| 77 | pub conversion_resolution_mode: Mode, | ||
| 78 | pub enable_wait_trigger: bool, | ||
| 79 | } | ||
| 80 | |||
| 81 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
| 82 | pub struct ConvTriggerConfig { | ||
| 83 | pub target_command_id: Tcmd, | ||
| 84 | pub delay_power: u8, | ||
| 85 | pub priority: Tpri, | ||
| 86 | pub enable_hardware_trigger: bool, | ||
| 87 | } | ||
| 88 | |||
| 89 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
| 90 | pub struct ConvResult { | ||
| 91 | pub command_id_source: u32, | ||
| 92 | pub loop_count_index: u32, | ||
| 93 | pub trigger_id_source: u32, | ||
| 94 | pub conv_value: u16, | ||
| 95 | } | ||
| 96 | |||
| 97 | pub struct Adc<I: Instance> { | ||
| 98 | _inst: core::marker::PhantomData<I>, | ||
| 99 | } | ||
| 100 | |||
| 101 | impl<I: Instance> Adc<I> { | ||
| 102 | /// initialize ADC | ||
| 103 | pub fn new(_inst: impl Instance, config: LpadcConfig) -> Self { | ||
| 104 | let adc = unsafe { &*I::ptr() }; | ||
| 105 | |||
| 106 | /* Reset the module. */ | ||
| 107 | adc.ctrl().modify(|_, w| w.rst().held_in_reset()); | ||
| 108 | adc.ctrl().modify(|_, w| w.rst().released_from_reset()); | ||
| 109 | |||
| 110 | adc.ctrl().modify(|_, w| w.rstfifo0().trigger_reset()); | ||
| 111 | |||
| 112 | /* Disable the module before setting configuration. */ | ||
| 113 | adc.ctrl().modify(|_, w| w.adcen().disabled()); | ||
| 114 | |||
| 115 | /* Configure the module generally. */ | ||
| 116 | if config.enable_in_doze_mode { | ||
| 117 | adc.ctrl().modify(|_, w| w.dozen().enabled()); | ||
| 118 | } else { | ||
| 119 | adc.ctrl().modify(|_, w| w.dozen().disabled()); | ||
| 120 | } | ||
| 121 | |||
| 122 | /* Set calibration average mode. */ | ||
| 123 | adc.ctrl() | ||
| 124 | .modify(|_, w| w.cal_avgs().variant(config.conversion_average_mode)); | ||
| 125 | |||
| 126 | adc.cfg().write(|w| unsafe { | ||
| 127 | let w = if config.enable_analog_preliminary { | ||
| 128 | w.pwren().pre_enabled() | ||
| 129 | } else { | ||
| 130 | w | ||
| 131 | }; | ||
| 132 | |||
| 133 | w.pudly() | ||
| 134 | .bits(config.power_up_delay) | ||
| 135 | .refsel() | ||
| 136 | .variant(config.reference_voltage_source) | ||
| 137 | .pwrsel() | ||
| 138 | .variant(config.power_level_mode) | ||
| 139 | .tprictrl() | ||
| 140 | .variant(match config.trigger_priority_policy { | ||
| 141 | TriggerPriorityPolicy::ConvPreemptSoftlyNotAutoResumed | ||
| 142 | | TriggerPriorityPolicy::ConvPreemptSoftlyAutoRestarted | ||
| 143 | | TriggerPriorityPolicy::ConvPreemptSoftlyAutoResumed => Tprictrl::FinishCurrentOnPriority, | ||
| 144 | TriggerPriorityPolicy::ConvPreemptSubsequentlyNotAutoResumed | ||
| 145 | | TriggerPriorityPolicy::ConvPreemptSubsequentlyAutoRestarted | ||
| 146 | | TriggerPriorityPolicy::ConvPreemptSubsequentlyAutoResumed => Tprictrl::FinishSequenceOnPriority, | ||
| 147 | _ => Tprictrl::AbortCurrentOnPriority, | ||
| 148 | }) | ||
| 149 | .tres() | ||
| 150 | .variant(match config.trigger_priority_policy { | ||
| 151 | TriggerPriorityPolicy::ConvPreemptImmediatelyAutoRestarted | ||
| 152 | | TriggerPriorityPolicy::ConvPreemptSoftlyAutoRestarted | ||
| 153 | | TriggerPriorityPolicy::ConvPreemptImmediatelyAutoResumed | ||
| 154 | | TriggerPriorityPolicy::ConvPreemptSoftlyAutoResumed | ||
| 155 | | TriggerPriorityPolicy::ConvPreemptSubsequentlyAutoRestarted | ||
| 156 | | TriggerPriorityPolicy::ConvPreemptSubsequentlyAutoResumed => Tres::Enabled, | ||
| 157 | _ => Tres::Disabled, | ||
| 158 | }) | ||
| 159 | .tcmdres() | ||
| 160 | .variant(match config.trigger_priority_policy { | ||
| 161 | TriggerPriorityPolicy::ConvPreemptImmediatelyAutoResumed | ||
| 162 | | TriggerPriorityPolicy::ConvPreemptSoftlyAutoResumed | ||
| 163 | | TriggerPriorityPolicy::ConvPreemptSubsequentlyAutoResumed | ||
| 164 | | TriggerPriorityPolicy::TriggerPriorityExceptionDisabled => Tcmdres::Enabled, | ||
| 165 | _ => Tcmdres::Disabled, | ||
| 166 | }) | ||
| 167 | .hpt_exdi() | ||
| 168 | .variant(match config.trigger_priority_policy { | ||
| 169 | TriggerPriorityPolicy::TriggerPriorityExceptionDisabled => HptExdi::Disabled, | ||
| 170 | _ => HptExdi::Enabled, | ||
| 171 | }) | ||
| 172 | }); | ||
| 173 | |||
| 174 | if config.enable_conv_pause { | ||
| 175 | adc.pause() | ||
| 176 | .modify(|_, w| unsafe { w.pauseen().enabled().pausedly().bits(config.conv_pause_delay) }); | ||
| 177 | } else { | ||
| 178 | adc.pause().write(|w| unsafe { w.bits(0) }); | ||
| 179 | } | ||
| 180 | |||
| 181 | adc.fctrl0() | ||
| 182 | .write(|w| unsafe { w.fwmark().bits(config.fifo_watermark) }); | ||
| 183 | |||
| 184 | // Enable ADC | ||
| 185 | adc.ctrl().modify(|_, w| w.adcen().enabled()); | ||
| 186 | |||
| 187 | Self { | ||
| 188 | _inst: core::marker::PhantomData, | ||
| 189 | } | ||
| 190 | } | ||
| 191 | |||
| 192 | pub fn deinit(&self) { | ||
| 193 | let adc = unsafe { &*I::ptr() }; | ||
| 194 | adc.ctrl().modify(|_, w| w.adcen().disabled()); | ||
| 195 | } | ||
| 196 | |||
| 197 | pub fn get_default_config() -> LpadcConfig { | ||
| 198 | LpadcConfig { | ||
| 199 | enable_in_doze_mode: true, | ||
| 200 | conversion_average_mode: CalAvgs::NoAverage, | ||
| 201 | enable_analog_preliminary: false, | ||
| 202 | power_up_delay: 0x80, | ||
| 203 | reference_voltage_source: Refsel::Option1, | ||
| 204 | power_level_mode: Pwrsel::Lowest, | ||
| 205 | trigger_priority_policy: TriggerPriorityPolicy::ConvPreemptImmediatelyNotAutoResumed, | ||
| 206 | enable_conv_pause: false, | ||
| 207 | conv_pause_delay: 0, | ||
| 208 | fifo_watermark: 0, | ||
| 209 | } | ||
| 210 | } | ||
| 211 | |||
| 212 | pub fn do_offset_calibration(&self) { | ||
| 213 | let adc = unsafe { &*I::ptr() }; | ||
| 214 | // Enable calibration mode | ||
| 215 | adc.ctrl() | ||
| 216 | .modify(|_, w| w.calofs().offset_calibration_request_pending()); | ||
| 217 | |||
| 218 | // Wait for calibration to complete (polling status register) | ||
| 219 | while adc.stat().read().cal_rdy().is_not_set() {} | ||
| 220 | } | ||
| 221 | |||
| 222 | pub fn get_gain_conv_result(&self, mut gain_adjustment: f32) -> u32 { | ||
| 223 | let mut gcra_array = [0u32; 17]; | ||
| 224 | let mut gcalr: u32 = 0; | ||
| 225 | |||
| 226 | for i in (1..=17).rev() { | ||
| 227 | let shift = 16 - (i - 1); | ||
| 228 | let step = 1.0 / (1u32 << shift) as f32; | ||
| 229 | let tmp = (gain_adjustment / step) as u32; | ||
| 230 | gcra_array[i - 1] = tmp; | ||
| 231 | gain_adjustment = gain_adjustment - tmp as f32 * step; | ||
| 232 | } | ||
| 233 | |||
| 234 | for i in (1..=17).rev() { | ||
| 235 | gcalr += gcra_array[i - 1] << (i - 1); | ||
| 236 | } | ||
| 237 | gcalr | ||
| 238 | } | ||
| 239 | |||
| 240 | pub fn do_auto_calibration(&self) { | ||
| 241 | let adc = unsafe { &*I::ptr() }; | ||
| 242 | adc.ctrl().modify(|_, w| w.cal_req().calibration_request_pending()); | ||
| 243 | |||
| 244 | while adc.gcc0().read().rdy().is_gain_cal_not_valid() {} | ||
| 245 | |||
| 246 | let mut gcca = adc.gcc0().read().gain_cal().bits() as u32; | ||
| 247 | if gcca & (((0xFFFF >> 0) + 1) >> 1) != 0 { | ||
| 248 | gcca |= !0xFFFF; | ||
| 249 | } | ||
| 250 | |||
| 251 | let gcra = 131072.0 / (131072.0 - gcca as f32); | ||
| 252 | |||
| 253 | // Write to GCR0 | ||
| 254 | adc.gcr0().write(|w| unsafe { w.bits(self.get_gain_conv_result(gcra)) }); | ||
| 255 | |||
| 256 | adc.gcr0().modify(|_, w| w.rdy().set_bit()); | ||
| 257 | |||
| 258 | // Wait for calibration to complete (polling status register) | ||
| 259 | while adc.stat().read().cal_rdy().is_not_set() {} | ||
| 260 | } | ||
| 261 | |||
| 262 | pub fn do_software_trigger(&self, trigger_id_mask: u32) { | ||
| 263 | let adc = unsafe { &*I::ptr() }; | ||
| 264 | adc.swtrig().write(|w| unsafe { w.bits(trigger_id_mask) }); | ||
| 265 | } | ||
| 266 | |||
| 267 | pub fn get_default_conv_command_config(&self) -> ConvCommandConfig { | ||
| 268 | ConvCommandConfig { | ||
| 269 | sample_channel_mode: Ctype::SingleEndedASideChannel, | ||
| 270 | channel_number: Adch::SelectCh0, | ||
| 271 | chained_next_command_number: Next::NoNextCmdTerminateOnFinish, | ||
| 272 | enable_auto_channel_increment: false, | ||
| 273 | loop_count: 0, | ||
| 274 | hardware_average_mode: Avgs::NoAverage, | ||
| 275 | sample_time_mode: Sts::Sample3p5, | ||
| 276 | hardware_compare_mode: Cmpen::DisabledAlwaysStoreResult, | ||
| 277 | hardware_compare_value_high: 0, | ||
| 278 | hardware_compare_value_low: 0, | ||
| 279 | conversion_resolution_mode: Mode::Data12Bits, | ||
| 280 | enable_wait_trigger: false, | ||
| 281 | } | ||
| 282 | } | ||
| 283 | |||
| 284 | //TBD Need to add cmdlx and cmdhx with x {2..7} | ||
| 285 | pub fn set_conv_command_config(&self, index: u32, config: &ConvCommandConfig) { | ||
| 286 | let adc = unsafe { &*I::ptr() }; | ||
| 287 | |||
| 288 | match index { | ||
| 289 | 1 => { | ||
| 290 | adc.cmdl1().write(|w| { | ||
| 291 | w.adch() | ||
| 292 | .variant(config.channel_number) | ||
| 293 | .mode() | ||
| 294 | .variant(config.conversion_resolution_mode) | ||
| 295 | }); | ||
| 296 | adc.cmdh1().write(|w| unsafe { | ||
| 297 | w.next() | ||
| 298 | .variant(config.chained_next_command_number) | ||
| 299 | .loop_() | ||
| 300 | .bits(config.loop_count) | ||
| 301 | .avgs() | ||
| 302 | .variant(config.hardware_average_mode) | ||
| 303 | .sts() | ||
| 304 | .variant(config.sample_time_mode) | ||
| 305 | .cmpen() | ||
| 306 | .variant(config.hardware_compare_mode); | ||
| 307 | if config.enable_wait_trigger { | ||
| 308 | w.wait_trig().enabled(); | ||
| 309 | } | ||
| 310 | if config.enable_auto_channel_increment { | ||
| 311 | w.lwi().enabled(); | ||
| 312 | } | ||
| 313 | w | ||
| 314 | }); | ||
| 315 | } | ||
| 316 | _ => panic!("Invalid command index: must be between 1 and 7"), | ||
| 317 | } | ||
| 318 | } | ||
| 319 | |||
| 320 | pub fn get_default_conv_trigger_config(&self) -> ConvTriggerConfig { | ||
| 321 | ConvTriggerConfig { | ||
| 322 | target_command_id: Tcmd::NotValid, | ||
| 323 | delay_power: 0, | ||
| 324 | priority: Tpri::HighestPriority, | ||
| 325 | enable_hardware_trigger: false, | ||
| 326 | } | ||
| 327 | } | ||
| 328 | |||
| 329 | pub fn set_conv_trigger_config(&self, trigger_id: usize, config: &ConvTriggerConfig) { | ||
| 330 | let adc = unsafe { &*I::ptr() }; | ||
| 331 | let tctrl = &adc.tctrl(trigger_id); | ||
| 332 | |||
| 333 | tctrl.write(|w| unsafe { | ||
| 334 | let w = w.tcmd().variant(config.target_command_id); | ||
| 335 | let w = w.tdly().bits(config.delay_power); | ||
| 336 | w.tpri().variant(config.priority); | ||
| 337 | if config.enable_hardware_trigger { | ||
| 338 | w.hten().enabled() | ||
| 339 | } else { | ||
| 340 | w | ||
| 341 | } | ||
| 342 | }); | ||
| 343 | } | ||
| 344 | |||
| 345 | pub fn do_reset_fifo(&self) { | ||
| 346 | let adc = unsafe { &*I::ptr() }; | ||
| 347 | adc.ctrl().modify(|_, w| w.rstfifo0().trigger_reset()); | ||
| 348 | } | ||
| 349 | |||
| 350 | pub fn enable_interrupt(&self, mask: u32) { | ||
| 351 | let adc = unsafe { &*I::ptr() }; | ||
| 352 | adc.ie().modify(|r, w| unsafe { w.bits(r.bits() | mask) }); | ||
| 353 | INTERRUPT_TRIGGERED.store(false, Ordering::SeqCst); | ||
| 354 | } | ||
| 355 | |||
| 356 | pub fn is_interrupt_triggered(&self) -> bool { | ||
| 357 | INTERRUPT_TRIGGERED.load(Ordering::Relaxed) | ||
| 358 | } | ||
| 359 | } | ||
| 360 | |||
| 361 | pub fn get_conv_result() -> Option<ConvResult> { | ||
| 362 | let adc = unsafe { &*pac::Adc1::ptr() }; | ||
| 363 | let fifo = adc.resfifo0().read().bits(); | ||
| 364 | const VALID_MASK: u32 = 1 << 31; | ||
| 365 | if fifo & VALID_MASK == 0 { | ||
| 366 | return None; | ||
| 367 | } | ||
| 368 | |||
| 369 | Some(ConvResult { | ||
| 370 | command_id_source: (fifo >> 24) & 0x0F, | ||
| 371 | loop_count_index: (fifo >> 20) & 0x0F, | ||
| 372 | trigger_id_source: (fifo >> 16) & 0x0F, | ||
| 373 | conv_value: (fifo & 0xFFFF) as u16, | ||
| 374 | }) | ||
| 375 | } | ||
| 376 | |||
| 377 | pub fn on_interrupt() { | ||
| 378 | if get_conv_result().is_some() { | ||
| 379 | INTERRUPT_TRIGGERED.store(true, Ordering::SeqCst); | ||
| 380 | } | ||
| 381 | } | ||
| 382 | |||
| 383 | pub struct AdcHandler; | ||
| 384 | impl crate::interrupt::typelevel::Handler<crate::interrupt::typelevel::ADC1> for AdcHandler { | ||
| 385 | unsafe fn on_interrupt() { | ||
| 386 | on_interrupt(); | ||
| 387 | } | ||
| 388 | } | ||
diff --git a/src/baremetal/mod.rs b/src/baremetal/mod.rs deleted file mode 100644 index c03b9538b..000000000 --- a/src/baremetal/mod.rs +++ /dev/null | |||
| @@ -1,6 +0,0 @@ | |||
| 1 | use core::panic::PanicInfo; | ||
| 2 | |||
| 3 | #[panic_handler] | ||
| 4 | fn panic(_info: &PanicInfo) -> ! { | ||
| 5 | loop {} | ||
| 6 | } | ||
diff --git a/src/board.rs b/src/board.rs new file mode 100644 index 000000000..fa679e82c --- /dev/null +++ b/src/board.rs | |||
| @@ -0,0 +1,14 @@ | |||
| 1 | use crate::{clocks, pac, pins}; | ||
| 2 | |||
| 3 | /// Initialize clocks and pin muxing for UART2 debug console. | ||
| 4 | pub unsafe fn init_uart2(p: &pac::Peripherals) { | ||
| 5 | clocks::ensure_frolf_running(p); | ||
| 6 | clocks::enable_uart2_port2(p); | ||
| 7 | pins::configure_uart2_pins_port2(); | ||
| 8 | clocks::select_uart2_clock(p); | ||
| 9 | } | ||
| 10 | |||
| 11 | /// Initialize clocks for the LED GPIO/PORT used by the blink example. | ||
| 12 | pub unsafe fn init_led(p: &pac::Peripherals) { | ||
| 13 | clocks::enable_led_port(p); | ||
| 14 | } | ||
diff --git a/src/clocks.rs b/src/clocks.rs new file mode 100644 index 000000000..65a17cef6 --- /dev/null +++ b/src/clocks.rs | |||
| @@ -0,0 +1,134 @@ | |||
| 1 | //! Clock control helpers (no magic numbers, PAC field access only). | ||
| 2 | //! Provides reusable gate abstractions for peripherals used by the examples. | ||
| 3 | use crate::pac; | ||
| 4 | |||
| 5 | /// Trait describing an AHB clock gate that can be toggled through MRCC. | ||
| 6 | pub trait Gate { | ||
| 7 | /// Enable the clock gate. | ||
| 8 | unsafe fn enable(mrcc: &pac::mrcc0::RegisterBlock); | ||
| 9 | |||
| 10 | /// Return whether the clock gate is currently enabled. | ||
| 11 | fn is_enabled(mrcc: &pac::mrcc0::RegisterBlock) -> bool; | ||
| 12 | } | ||
| 13 | |||
| 14 | /// Enable a clock gate for the given peripheral set. | ||
| 15 | #[inline] | ||
| 16 | pub unsafe fn enable<G: Gate>(peripherals: &pac::Peripherals) { | ||
| 17 | let mrcc = &peripherals.mrcc0; | ||
| 18 | G::enable(mrcc); | ||
| 19 | while !G::is_enabled(mrcc) {} | ||
| 20 | core::arch::asm!("dsb sy; isb sy", options(nomem, nostack, preserves_flags)); | ||
| 21 | } | ||
| 22 | |||
| 23 | /// Check whether a gate is currently enabled. | ||
| 24 | #[inline] | ||
| 25 | pub fn is_enabled<G: Gate>(peripherals: &pac::Peripherals) -> bool { | ||
| 26 | G::is_enabled(&peripherals.mrcc0) | ||
| 27 | } | ||
| 28 | |||
| 29 | macro_rules! impl_cc_gate { | ||
| 30 | ($name:ident, $reg:ident, $field:ident) => { | ||
| 31 | pub struct $name; | ||
| 32 | |||
| 33 | impl Gate for $name { | ||
| 34 | #[inline] | ||
| 35 | unsafe fn enable(mrcc: &pac::mrcc0::RegisterBlock) { | ||
| 36 | mrcc.$reg().modify(|_, w| w.$field().enabled()); | ||
| 37 | } | ||
| 38 | |||
| 39 | #[inline] | ||
| 40 | fn is_enabled(mrcc: &pac::mrcc0::RegisterBlock) -> bool { | ||
| 41 | mrcc.$reg().read().$field().is_enabled() | ||
| 42 | } | ||
| 43 | } | ||
| 44 | }; | ||
| 45 | } | ||
| 46 | |||
| 47 | pub mod gate { | ||
| 48 | use super::*; | ||
| 49 | |||
| 50 | impl_cc_gate!(Port2, mrcc_glb_cc1, port2); | ||
| 51 | impl_cc_gate!(Port3, mrcc_glb_cc1, port3); | ||
| 52 | impl_cc_gate!(Ostimer0, mrcc_glb_cc1, ostimer0); | ||
| 53 | impl_cc_gate!(Lpuart2, mrcc_glb_cc0, lpuart2); | ||
| 54 | impl_cc_gate!(Gpio3, mrcc_glb_cc2, gpio3); | ||
| 55 | impl_cc_gate!(Port1, mrcc_glb_cc1, port1); | ||
| 56 | impl_cc_gate!(Adc1, mrcc_glb_cc1, adc1); | ||
| 57 | } | ||
| 58 | |||
| 59 | /// Convenience helper enabling the PORT2 and LPUART2 gates required for the debug UART. | ||
| 60 | pub unsafe fn enable_uart2_port2(peripherals: &pac::Peripherals) { | ||
| 61 | enable::<gate::Port2>(peripherals); | ||
| 62 | enable::<gate::Lpuart2>(peripherals); | ||
| 63 | } | ||
| 64 | |||
| 65 | /// Convenience helper enabling the PORT3 and GPIO3 gates used by the LED in the examples. | ||
| 66 | pub unsafe fn enable_led_port(peripherals: &pac::Peripherals) { | ||
| 67 | enable::<gate::Port3>(peripherals); | ||
| 68 | enable::<gate::Gpio3>(peripherals); | ||
| 69 | } | ||
| 70 | |||
| 71 | /// Convenience helper enabling the OSTIMER0 clock gate. | ||
| 72 | pub unsafe fn enable_ostimer0(peripherals: &pac::Peripherals) { | ||
| 73 | enable::<gate::Ostimer0>(peripherals); | ||
| 74 | } | ||
| 75 | |||
| 76 | pub unsafe fn select_uart2_clock(peripherals: &pac::Peripherals) { | ||
| 77 | // Use FRO_LF_DIV (already running) MUX=0 DIV=0 | ||
| 78 | let mrcc = &peripherals.mrcc0; | ||
| 79 | mrcc.mrcc_lpuart2_clksel().write(|w| w.mux().clkroot_func_0()); | ||
| 80 | mrcc.mrcc_lpuart2_clkdiv().write(|w| unsafe { w.bits(0) }); | ||
| 81 | } | ||
| 82 | |||
| 83 | pub unsafe fn ensure_frolf_running(peripherals: &pac::Peripherals) { | ||
| 84 | // Ensure FRO_LF divider clock is running (reset default HALT=1 stops it) | ||
| 85 | let sys = &peripherals.syscon; | ||
| 86 | sys.frolfdiv().modify(|_, w| { | ||
| 87 | // DIV defaults to 0; keep it explicit and clear HALT | ||
| 88 | unsafe { w.div().bits(0) }.halt().run() | ||
| 89 | }); | ||
| 90 | } | ||
| 91 | |||
| 92 | /// Compute the FRO_LF_DIV output frequency currently selected for LPUART2. | ||
| 93 | /// Assumes select_uart2_clock() has chosen MUX=0 (FRO_LF_DIV) and DIV is set in SYSCON.FRO_LF_DIV. | ||
| 94 | pub unsafe fn uart2_src_hz(peripherals: &pac::Peripherals) -> u32 { | ||
| 95 | // SYSCON.FRO_LF_DIV: DIV field is simple divider: freq_out = 12_000_000 / (DIV+1) for many NXP parts. | ||
| 96 | // On MCXA276 FRO_LF base is 12 MHz; our init keeps DIV=0, so result=12_000_000. | ||
| 97 | // Read it anyway for future generality. | ||
| 98 | let div = peripherals.syscon.frolfdiv().read().div().bits() as u32; | ||
| 99 | let base = 12_000_000u32; | ||
| 100 | base / (div + 1) | ||
| 101 | } | ||
| 102 | |||
| 103 | /// Enable clock gate and release reset for OSTIMER0. | ||
| 104 | /// Select OSTIMER0 clock source = 1 MHz root (working bring-up configuration). | ||
| 105 | pub unsafe fn select_ostimer0_clock_1m(peripherals: &pac::Peripherals) { | ||
| 106 | let mrcc = &peripherals.mrcc0; | ||
| 107 | mrcc.mrcc_ostimer0_clksel().write(|w| w.mux().clkroot_1m()); | ||
| 108 | } | ||
| 109 | |||
| 110 | pub unsafe fn init_fro16k(peripherals: &pac::Peripherals) { | ||
| 111 | let vbat = &peripherals.vbat0; | ||
| 112 | // Enable FRO16K oscillator | ||
| 113 | vbat.froctla().modify(|_, w| w.fro_en().set_bit()); | ||
| 114 | |||
| 115 | // Lock the control register | ||
| 116 | vbat.frolcka().modify(|_, w| w.lock().set_bit()); | ||
| 117 | |||
| 118 | // Enable clock outputs to both VSYS and VDD_CORE domains | ||
| 119 | // Bit 0: clk_16k0 to VSYS domain | ||
| 120 | // Bit 1: clk_16k1 to VDD_CORE domain | ||
| 121 | vbat.froclke().modify(|_, w| unsafe { w.clke().bits(0x3) }); | ||
| 122 | } | ||
| 123 | |||
| 124 | pub unsafe fn enable_adc(peripherals: &pac::Peripherals) { | ||
| 125 | enable::<gate::Port1>(peripherals); | ||
| 126 | enable::<gate::Adc1>(peripherals); | ||
| 127 | } | ||
| 128 | |||
| 129 | pub unsafe fn select_adc_clock(peripherals: &pac::Peripherals) { | ||
| 130 | // Use FRO_LF_DIV (already running) MUX=0 DIV=0 | ||
| 131 | let mrcc = &peripherals.mrcc0; | ||
| 132 | mrcc.mrcc_adc_clksel().write(|w| w.mux().clkroot_func_0()); | ||
| 133 | mrcc.mrcc_adc_clkdiv().write(|w| unsafe { w.bits(0) }); | ||
| 134 | } | ||
diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 000000000..93aed5a99 --- /dev/null +++ b/src/config.rs | |||
| @@ -0,0 +1,20 @@ | |||
| 1 | // HAL configuration (minimal), mirroring embassy-imxrt style | ||
| 2 | |||
| 3 | use crate::interrupt::Priority; | ||
| 4 | |||
| 5 | #[non_exhaustive] | ||
| 6 | pub struct Config { | ||
| 7 | pub time_interrupt_priority: Priority, | ||
| 8 | pub rtc_interrupt_priority: Priority, | ||
| 9 | pub adc_interrupt_priority: Priority, | ||
| 10 | } | ||
| 11 | |||
| 12 | impl Default for Config { | ||
| 13 | fn default() -> Self { | ||
| 14 | Self { | ||
| 15 | time_interrupt_priority: Priority::from(0), | ||
| 16 | rtc_interrupt_priority: Priority::from(0), | ||
| 17 | adc_interrupt_priority: Priority::from(0), | ||
| 18 | } | ||
| 19 | } | ||
| 20 | } | ||
diff --git a/src/gpio.rs b/src/gpio.rs new file mode 100644 index 000000000..1e7214b28 --- /dev/null +++ b/src/gpio.rs | |||
| @@ -0,0 +1,244 @@ | |||
| 1 | //! GPIO driver built around a type-erased `Flex` pin, similar to other Embassy HALs. | ||
| 2 | //! The exported `Output`/`Input` drivers own a `Flex` so they no longer depend on the | ||
| 3 | //! concrete pin type. | ||
| 4 | |||
| 5 | use core::marker::PhantomData; | ||
| 6 | |||
| 7 | use crate::{pac, pins as pin_config}; | ||
| 8 | |||
| 9 | /// Logical level for GPIO pins. | ||
| 10 | #[derive(Copy, Clone, Eq, PartialEq, Debug)] | ||
| 11 | pub enum Level { | ||
| 12 | Low, | ||
| 13 | High, | ||
| 14 | } | ||
| 15 | |||
| 16 | pub type Gpio = crate::peripherals::GPIO; | ||
| 17 | |||
| 18 | /// Type-erased representation of a GPIO pin. | ||
| 19 | #[derive(Copy, Clone)] | ||
| 20 | pub struct AnyPin { | ||
| 21 | port: u8, | ||
| 22 | pin: u8, | ||
| 23 | gpio: *const pac::gpio0::RegisterBlock, | ||
| 24 | } | ||
| 25 | |||
| 26 | impl AnyPin { | ||
| 27 | /// Create an `AnyPin` from raw components. | ||
| 28 | pub fn new(port: u8, pin: u8, gpio: *const pac::gpio0::RegisterBlock) -> Self { | ||
| 29 | Self { port, pin, gpio } | ||
| 30 | } | ||
| 31 | |||
| 32 | #[inline(always)] | ||
| 33 | fn mask(&self) -> u32 { | ||
| 34 | 1u32 << self.pin | ||
| 35 | } | ||
| 36 | |||
| 37 | #[inline(always)] | ||
| 38 | fn gpio(&self) -> &'static pac::gpio0::RegisterBlock { | ||
| 39 | unsafe { &*self.gpio } | ||
| 40 | } | ||
| 41 | |||
| 42 | #[inline(always)] | ||
| 43 | pub fn port_index(&self) -> u8 { | ||
| 44 | self.port | ||
| 45 | } | ||
| 46 | |||
| 47 | #[inline(always)] | ||
| 48 | pub fn pin_index(&self) -> u8 { | ||
| 49 | self.pin | ||
| 50 | } | ||
| 51 | } | ||
| 52 | |||
| 53 | /// Type-level trait implemented by concrete pin ZSTs. | ||
| 54 | pub trait PinId { | ||
| 55 | fn port_index() -> u8; | ||
| 56 | fn pin_index() -> u8; | ||
| 57 | fn gpio_ptr() -> *const pac::gpio0::RegisterBlock; | ||
| 58 | |||
| 59 | fn set_mux_gpio() { | ||
| 60 | unsafe { pin_config::set_pin_mux_gpio(Self::port_index(), Self::pin_index()) } | ||
| 61 | } | ||
| 62 | |||
| 63 | fn degrade() -> AnyPin { | ||
| 64 | AnyPin::new(Self::port_index(), Self::pin_index(), Self::gpio_ptr()) | ||
| 65 | } | ||
| 66 | } | ||
| 67 | |||
| 68 | pub mod pins { | ||
| 69 | use super::{pac, AnyPin, PinId}; | ||
| 70 | |||
| 71 | macro_rules! define_pin { | ||
| 72 | ($Name:ident, $port:literal, $pin:literal, $GpioBlk:ident) => { | ||
| 73 | pub struct $Name; | ||
| 74 | impl super::PinId for $Name { | ||
| 75 | #[inline(always)] | ||
| 76 | fn port_index() -> u8 { | ||
| 77 | $port | ||
| 78 | } | ||
| 79 | #[inline(always)] | ||
| 80 | fn pin_index() -> u8 { | ||
| 81 | $pin | ||
| 82 | } | ||
| 83 | #[inline(always)] | ||
| 84 | fn gpio_ptr() -> *const pac::gpio0::RegisterBlock { | ||
| 85 | pac::$GpioBlk::ptr() | ||
| 86 | } | ||
| 87 | } | ||
| 88 | |||
| 89 | impl $Name { | ||
| 90 | /// Convenience helper to obtain a type-erased handle to this pin. | ||
| 91 | pub fn degrade() -> AnyPin { | ||
| 92 | <Self as PinId>::degrade() | ||
| 93 | } | ||
| 94 | |||
| 95 | pub fn set_mux_gpio() { | ||
| 96 | <Self as PinId>::set_mux_gpio() | ||
| 97 | } | ||
| 98 | } | ||
| 99 | }; | ||
| 100 | } | ||
| 101 | |||
| 102 | // Extend this list as more pins are needed. | ||
| 103 | define_pin!(PIO3_18, 3, 18, Gpio3); | ||
| 104 | } | ||
| 105 | |||
| 106 | /// A flexible pin that can be configured as input or output. | ||
| 107 | pub struct Flex<'d> { | ||
| 108 | pin: AnyPin, | ||
| 109 | _marker: PhantomData<&'d mut ()>, | ||
| 110 | } | ||
| 111 | |||
| 112 | impl<'d> Flex<'d> { | ||
| 113 | pub fn new(pin: AnyPin) -> Self { | ||
| 114 | Self { | ||
| 115 | pin, | ||
| 116 | _marker: PhantomData, | ||
| 117 | } | ||
| 118 | } | ||
| 119 | |||
| 120 | #[inline(always)] | ||
| 121 | fn gpio(&self) -> &'static pac::gpio0::RegisterBlock { | ||
| 122 | self.pin.gpio() | ||
| 123 | } | ||
| 124 | |||
| 125 | #[inline(always)] | ||
| 126 | fn mask(&self) -> u32 { | ||
| 127 | self.pin.mask() | ||
| 128 | } | ||
| 129 | |||
| 130 | pub fn set_as_input(&mut self) { | ||
| 131 | let mask = self.mask(); | ||
| 132 | let gpio = self.gpio(); | ||
| 133 | gpio.pddr().modify(|r, w| unsafe { w.bits(r.bits() & !mask) }); | ||
| 134 | } | ||
| 135 | |||
| 136 | pub fn set_as_output(&mut self) { | ||
| 137 | let mask = self.mask(); | ||
| 138 | let gpio = self.gpio(); | ||
| 139 | gpio.pddr().modify(|r, w| unsafe { w.bits(r.bits() | mask) }); | ||
| 140 | } | ||
| 141 | |||
| 142 | pub fn set_high(&mut self) { | ||
| 143 | self.gpio().psor().write(|w| unsafe { w.bits(self.mask()) }); | ||
| 144 | } | ||
| 145 | |||
| 146 | pub fn set_low(&mut self) { | ||
| 147 | self.gpio().pcor().write(|w| unsafe { w.bits(self.mask()) }); | ||
| 148 | } | ||
| 149 | |||
| 150 | pub fn set_level(&mut self, level: Level) { | ||
| 151 | match level { | ||
| 152 | Level::High => self.set_high(), | ||
| 153 | Level::Low => self.set_low(), | ||
| 154 | } | ||
| 155 | } | ||
| 156 | |||
| 157 | pub fn toggle(&mut self) { | ||
| 158 | self.gpio().ptor().write(|w| unsafe { w.bits(self.mask()) }); | ||
| 159 | } | ||
| 160 | |||
| 161 | pub fn is_high(&self) -> bool { | ||
| 162 | (self.gpio().pdir().read().bits() & self.mask()) != 0 | ||
| 163 | } | ||
| 164 | |||
| 165 | pub fn is_low(&self) -> bool { | ||
| 166 | !self.is_high() | ||
| 167 | } | ||
| 168 | } | ||
| 169 | |||
| 170 | /// GPIO output driver that owns a `Flex` pin. | ||
| 171 | pub struct Output<'d> { | ||
| 172 | flex: Flex<'d>, | ||
| 173 | } | ||
| 174 | |||
| 175 | impl<'d> Output<'d> { | ||
| 176 | pub fn new(pin: AnyPin, initial: Level) -> Self { | ||
| 177 | let mut flex = Flex::new(pin); | ||
| 178 | flex.set_level(initial); | ||
| 179 | flex.set_as_output(); | ||
| 180 | Self { flex } | ||
| 181 | } | ||
| 182 | |||
| 183 | #[inline] | ||
| 184 | pub fn set_high(&mut self) { | ||
| 185 | self.flex.set_high(); | ||
| 186 | } | ||
| 187 | |||
| 188 | #[inline] | ||
| 189 | pub fn set_low(&mut self) { | ||
| 190 | self.flex.set_low(); | ||
| 191 | } | ||
| 192 | |||
| 193 | #[inline] | ||
| 194 | pub fn set_level(&mut self, level: Level) { | ||
| 195 | self.flex.set_level(level); | ||
| 196 | } | ||
| 197 | |||
| 198 | #[inline] | ||
| 199 | pub fn toggle(&mut self) { | ||
| 200 | self.flex.toggle(); | ||
| 201 | } | ||
| 202 | |||
| 203 | #[inline] | ||
| 204 | pub fn is_set_high(&self) -> bool { | ||
| 205 | self.flex.is_high() | ||
| 206 | } | ||
| 207 | |||
| 208 | #[inline] | ||
| 209 | pub fn is_set_low(&self) -> bool { | ||
| 210 | !self.is_set_high() | ||
| 211 | } | ||
| 212 | |||
| 213 | /// Expose the inner `Flex` if callers need to reconfigure the pin. | ||
| 214 | pub fn into_flex(self) -> Flex<'d> { | ||
| 215 | self.flex | ||
| 216 | } | ||
| 217 | } | ||
| 218 | |||
| 219 | /// GPIO input driver that owns a `Flex` pin. | ||
| 220 | pub struct Input<'d> { | ||
| 221 | flex: Flex<'d>, | ||
| 222 | } | ||
| 223 | |||
| 224 | impl<'d> Input<'d> { | ||
| 225 | pub fn new(pin: AnyPin) -> Self { | ||
| 226 | let mut flex = Flex::new(pin); | ||
| 227 | flex.set_as_input(); | ||
| 228 | Self { flex } | ||
| 229 | } | ||
| 230 | |||
| 231 | #[inline] | ||
| 232 | pub fn is_high(&self) -> bool { | ||
| 233 | self.flex.is_high() | ||
| 234 | } | ||
| 235 | |||
| 236 | #[inline] | ||
| 237 | pub fn is_low(&self) -> bool { | ||
| 238 | self.flex.is_low() | ||
| 239 | } | ||
| 240 | |||
| 241 | pub fn into_flex(self) -> Flex<'d> { | ||
| 242 | self.flex | ||
| 243 | } | ||
| 244 | } | ||
diff --git a/src/interrupt.rs b/src/interrupt.rs new file mode 100644 index 000000000..09d7acbef --- /dev/null +++ b/src/interrupt.rs | |||
| @@ -0,0 +1,349 @@ | |||
| 1 | //! Minimal interrupt helpers mirroring embassy-imxrt style for OS_EVENT and LPUART2. | ||
| 2 | //! Type-level interrupt traits and bindings are provided by the | ||
| 3 | //! `embassy_hal_internal::interrupt_mod!` macro via the generated module below. | ||
| 4 | |||
| 5 | mod generated { | ||
| 6 | embassy_hal_internal::interrupt_mod!(OS_EVENT, LPUART2, RTC, ADC1); | ||
| 7 | } | ||
| 8 | |||
| 9 | use core::sync::atomic::{AtomicU16, AtomicU32, Ordering}; | ||
| 10 | |||
| 11 | pub use generated::interrupt::{typelevel, Priority}; | ||
| 12 | |||
| 13 | use crate::pac::Interrupt; | ||
| 14 | |||
| 15 | /// Trait for configuring and controlling interrupts. | ||
| 16 | /// | ||
| 17 | /// This trait provides a consistent interface for interrupt management across | ||
| 18 | /// different interrupt sources, similar to embassy-imxrt's InterruptExt. | ||
| 19 | pub trait InterruptExt { | ||
| 20 | /// Clear any pending interrupt in NVIC. | ||
| 21 | fn unpend(&self); | ||
| 22 | |||
| 23 | /// Set NVIC priority for this interrupt. | ||
| 24 | fn set_priority(&self, priority: Priority); | ||
| 25 | |||
| 26 | /// Enable this interrupt in NVIC. | ||
| 27 | /// | ||
| 28 | /// # Safety | ||
| 29 | /// This function is unsafe because it can enable interrupts that may not be | ||
| 30 | /// properly configured, potentially leading to undefined behavior. | ||
| 31 | unsafe fn enable(&self); | ||
| 32 | |||
| 33 | /// Disable this interrupt in NVIC. | ||
| 34 | /// | ||
| 35 | /// # Safety | ||
| 36 | /// This function is unsafe because disabling interrupts may leave the system | ||
| 37 | /// in an inconsistent state if the interrupt was expected to fire. | ||
| 38 | unsafe fn disable(&self); | ||
| 39 | |||
| 40 | /// Check if the interrupt is pending in NVIC. | ||
| 41 | fn is_pending(&self) -> bool; | ||
| 42 | } | ||
| 43 | |||
| 44 | #[derive(Clone, Copy, Debug, Default)] | ||
| 45 | pub struct DefaultHandlerSnapshot { | ||
| 46 | pub vector: u16, | ||
| 47 | pub count: u32, | ||
| 48 | pub cfsr: u32, | ||
| 49 | pub hfsr: u32, | ||
| 50 | pub stacked_pc: u32, | ||
| 51 | pub stacked_lr: u32, | ||
| 52 | pub stacked_sp: u32, | ||
| 53 | } | ||
| 54 | |||
| 55 | static LAST_DEFAULT_VECTOR: AtomicU16 = AtomicU16::new(0); | ||
| 56 | static LAST_DEFAULT_COUNT: AtomicU32 = AtomicU32::new(0); | ||
| 57 | static LAST_DEFAULT_CFSR: AtomicU32 = AtomicU32::new(0); | ||
| 58 | static LAST_DEFAULT_HFSR: AtomicU32 = AtomicU32::new(0); | ||
| 59 | static LAST_DEFAULT_PC: AtomicU32 = AtomicU32::new(0); | ||
| 60 | static LAST_DEFAULT_LR: AtomicU32 = AtomicU32::new(0); | ||
| 61 | static LAST_DEFAULT_SP: AtomicU32 = AtomicU32::new(0); | ||
| 62 | |||
| 63 | #[inline] | ||
| 64 | pub fn default_handler_snapshot() -> DefaultHandlerSnapshot { | ||
| 65 | DefaultHandlerSnapshot { | ||
| 66 | vector: LAST_DEFAULT_VECTOR.load(Ordering::Relaxed), | ||
| 67 | count: LAST_DEFAULT_COUNT.load(Ordering::Relaxed), | ||
| 68 | cfsr: LAST_DEFAULT_CFSR.load(Ordering::Relaxed), | ||
| 69 | hfsr: LAST_DEFAULT_HFSR.load(Ordering::Relaxed), | ||
| 70 | stacked_pc: LAST_DEFAULT_PC.load(Ordering::Relaxed), | ||
| 71 | stacked_lr: LAST_DEFAULT_LR.load(Ordering::Relaxed), | ||
| 72 | stacked_sp: LAST_DEFAULT_SP.load(Ordering::Relaxed), | ||
| 73 | } | ||
| 74 | } | ||
| 75 | |||
| 76 | #[inline] | ||
| 77 | pub fn clear_default_handler_snapshot() { | ||
| 78 | LAST_DEFAULT_VECTOR.store(0, Ordering::Relaxed); | ||
| 79 | LAST_DEFAULT_COUNT.store(0, Ordering::Relaxed); | ||
| 80 | LAST_DEFAULT_CFSR.store(0, Ordering::Relaxed); | ||
| 81 | LAST_DEFAULT_HFSR.store(0, Ordering::Relaxed); | ||
| 82 | LAST_DEFAULT_PC.store(0, Ordering::Relaxed); | ||
| 83 | LAST_DEFAULT_LR.store(0, Ordering::Relaxed); | ||
| 84 | LAST_DEFAULT_SP.store(0, Ordering::Relaxed); | ||
| 85 | } | ||
| 86 | |||
| 87 | /// OS_EVENT interrupt helper with methods similar to embassy-imxrt's InterruptExt. | ||
| 88 | pub struct OsEvent; | ||
| 89 | pub const OS_EVENT: OsEvent = OsEvent; | ||
| 90 | |||
| 91 | impl InterruptExt for OsEvent { | ||
| 92 | /// Clear any pending OS_EVENT in NVIC. | ||
| 93 | #[inline] | ||
| 94 | fn unpend(&self) { | ||
| 95 | cortex_m::peripheral::NVIC::unpend(Interrupt::OS_EVENT); | ||
| 96 | } | ||
| 97 | |||
| 98 | /// Set NVIC priority for OS_EVENT. | ||
| 99 | #[inline] | ||
| 100 | fn set_priority(&self, priority: Priority) { | ||
| 101 | unsafe { | ||
| 102 | let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC; | ||
| 103 | nvic.set_priority(Interrupt::OS_EVENT, u8::from(priority)); | ||
| 104 | } | ||
| 105 | } | ||
| 106 | |||
| 107 | /// Enable OS_EVENT in NVIC. | ||
| 108 | #[inline] | ||
| 109 | unsafe fn enable(&self) { | ||
| 110 | cortex_m::peripheral::NVIC::unmask(Interrupt::OS_EVENT); | ||
| 111 | } | ||
| 112 | |||
| 113 | /// Disable OS_EVENT in NVIC. | ||
| 114 | #[inline] | ||
| 115 | unsafe fn disable(&self) { | ||
| 116 | cortex_m::peripheral::NVIC::mask(Interrupt::OS_EVENT); | ||
| 117 | } | ||
| 118 | |||
| 119 | /// Check if OS_EVENT is pending in NVIC. | ||
| 120 | #[inline] | ||
| 121 | fn is_pending(&self) -> bool { | ||
| 122 | cortex_m::peripheral::NVIC::is_pending(Interrupt::OS_EVENT) | ||
| 123 | } | ||
| 124 | } | ||
| 125 | |||
| 126 | impl OsEvent { | ||
| 127 | /// Configure OS_EVENT interrupt for timer operation. | ||
| 128 | /// This sets up the NVIC priority, enables the interrupt, and ensures global interrupts are enabled. | ||
| 129 | /// Also performs a software event to wake any pending WFE. | ||
| 130 | pub fn configure_for_timer(&self, priority: Priority) { | ||
| 131 | // Configure NVIC | ||
| 132 | self.unpend(); | ||
| 133 | self.set_priority(priority); | ||
| 134 | unsafe { | ||
| 135 | self.enable(); | ||
| 136 | } | ||
| 137 | |||
| 138 | // Ensure global interrupts are enabled in no-reset scenarios (e.g., cargo run) | ||
| 139 | // Debuggers typically perform a reset which leaves PRIMASK=0; cargo run may not. | ||
| 140 | unsafe { | ||
| 141 | cortex_m::interrupt::enable(); | ||
| 142 | } | ||
| 143 | |||
| 144 | // Wake any executor WFE that might be sleeping when we armed the first deadline | ||
| 145 | cortex_m::asm::sev(); | ||
| 146 | } | ||
| 147 | } | ||
| 148 | |||
| 149 | /// LPUART2 interrupt helper with methods similar to embassy-imxrt's InterruptExt. | ||
| 150 | pub struct Lpuart2; | ||
| 151 | pub const LPUART2: Lpuart2 = Lpuart2; | ||
| 152 | |||
| 153 | impl InterruptExt for Lpuart2 { | ||
| 154 | /// Clear any pending LPUART2 in NVIC. | ||
| 155 | #[inline] | ||
| 156 | fn unpend(&self) { | ||
| 157 | cortex_m::peripheral::NVIC::unpend(Interrupt::LPUART2); | ||
| 158 | } | ||
| 159 | |||
| 160 | /// Set NVIC priority for LPUART2. | ||
| 161 | #[inline] | ||
| 162 | fn set_priority(&self, priority: Priority) { | ||
| 163 | unsafe { | ||
| 164 | let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC; | ||
| 165 | nvic.set_priority(Interrupt::LPUART2, u8::from(priority)); | ||
| 166 | } | ||
| 167 | } | ||
| 168 | |||
| 169 | /// Enable LPUART2 in NVIC. | ||
| 170 | #[inline] | ||
| 171 | unsafe fn enable(&self) { | ||
| 172 | cortex_m::peripheral::NVIC::unmask(Interrupt::LPUART2); | ||
| 173 | } | ||
| 174 | |||
| 175 | /// Disable LPUART2 in NVIC. | ||
| 176 | #[inline] | ||
| 177 | unsafe fn disable(&self) { | ||
| 178 | cortex_m::peripheral::NVIC::mask(Interrupt::LPUART2); | ||
| 179 | } | ||
| 180 | |||
| 181 | /// Check if LPUART2 is pending in NVIC. | ||
| 182 | #[inline] | ||
| 183 | fn is_pending(&self) -> bool { | ||
| 184 | cortex_m::peripheral::NVIC::is_pending(Interrupt::LPUART2) | ||
| 185 | } | ||
| 186 | } | ||
| 187 | |||
| 188 | impl Lpuart2 { | ||
| 189 | /// Configure LPUART2 interrupt for UART operation. | ||
| 190 | /// This sets up the NVIC priority, enables the interrupt, and ensures global interrupts are enabled. | ||
| 191 | pub fn configure_for_uart(&self, priority: Priority) { | ||
| 192 | // Configure NVIC | ||
| 193 | self.unpend(); | ||
| 194 | self.set_priority(priority); | ||
| 195 | unsafe { | ||
| 196 | self.enable(); | ||
| 197 | } | ||
| 198 | |||
| 199 | // Ensure global interrupts are enabled in no-reset scenarios (e.g., cargo run) | ||
| 200 | // Debuggers typically perform a reset which leaves PRIMASK=0; cargo run may not. | ||
| 201 | unsafe { | ||
| 202 | cortex_m::interrupt::enable(); | ||
| 203 | } | ||
| 204 | } | ||
| 205 | |||
| 206 | /// Install LPUART2 handler into the RAM vector table. | ||
| 207 | /// Safety: See `install_irq_handler`. | ||
| 208 | pub unsafe fn install_handler(&self, handler: unsafe extern "C" fn()) { | ||
| 209 | install_irq_handler(Interrupt::LPUART2, handler); | ||
| 210 | } | ||
| 211 | } | ||
| 212 | |||
| 213 | pub struct Rtc; | ||
| 214 | pub const RTC: Rtc = Rtc; | ||
| 215 | |||
| 216 | impl InterruptExt for Rtc { | ||
| 217 | /// Clear any pending RTC in NVIC. | ||
| 218 | #[inline] | ||
| 219 | fn unpend(&self) { | ||
| 220 | cortex_m::peripheral::NVIC::unpend(Interrupt::RTC); | ||
| 221 | } | ||
| 222 | |||
| 223 | /// Set NVIC priority for RTC. | ||
| 224 | #[inline] | ||
| 225 | fn set_priority(&self, priority: Priority) { | ||
| 226 | unsafe { | ||
| 227 | let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC; | ||
| 228 | nvic.set_priority(Interrupt::RTC, u8::from(priority)); | ||
| 229 | } | ||
| 230 | } | ||
| 231 | |||
| 232 | /// Enable RTC in NVIC. | ||
| 233 | #[inline] | ||
| 234 | unsafe fn enable(&self) { | ||
| 235 | cortex_m::peripheral::NVIC::unmask(Interrupt::RTC); | ||
| 236 | } | ||
| 237 | |||
| 238 | /// Disable RTC in NVIC. | ||
| 239 | #[inline] | ||
| 240 | unsafe fn disable(&self) { | ||
| 241 | cortex_m::peripheral::NVIC::mask(Interrupt::RTC); | ||
| 242 | } | ||
| 243 | |||
| 244 | /// Check if RTC is pending in NVIC. | ||
| 245 | #[inline] | ||
| 246 | fn is_pending(&self) -> bool { | ||
| 247 | cortex_m::peripheral::NVIC::is_pending(Interrupt::RTC) | ||
| 248 | } | ||
| 249 | } | ||
| 250 | |||
| 251 | pub struct Adc; | ||
| 252 | pub const ADC1: Adc = Adc; | ||
| 253 | |||
| 254 | impl InterruptExt for Adc { | ||
| 255 | /// Clear any pending ADC1 in NVIC. | ||
| 256 | #[inline] | ||
| 257 | fn unpend(&self) { | ||
| 258 | cortex_m::peripheral::NVIC::unpend(Interrupt::ADC1); | ||
| 259 | } | ||
| 260 | |||
| 261 | /// Set NVIC priority for ADC1. | ||
| 262 | #[inline] | ||
| 263 | fn set_priority(&self, priority: Priority) { | ||
| 264 | unsafe { | ||
| 265 | let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC; | ||
| 266 | nvic.set_priority(Interrupt::ADC1, u8::from(priority)); | ||
| 267 | } | ||
| 268 | } | ||
| 269 | |||
| 270 | /// Enable ADC1 in NVIC. | ||
| 271 | #[inline] | ||
| 272 | unsafe fn enable(&self) { | ||
| 273 | cortex_m::peripheral::NVIC::unmask(Interrupt::ADC1); | ||
| 274 | } | ||
| 275 | |||
| 276 | /// Disable ADC1 in NVIC. | ||
| 277 | #[inline] | ||
| 278 | unsafe fn disable(&self) { | ||
| 279 | cortex_m::peripheral::NVIC::mask(Interrupt::ADC1); | ||
| 280 | } | ||
| 281 | |||
| 282 | /// Check if ADC1 is pending in NVIC. | ||
| 283 | #[inline] | ||
| 284 | fn is_pending(&self) -> bool { | ||
| 285 | cortex_m::peripheral::NVIC::is_pending(Interrupt::ADC1) | ||
| 286 | } | ||
| 287 | } | ||
| 288 | |||
| 289 | /// Set VTOR (Vector Table Offset) to a RAM-based vector table. | ||
| 290 | /// Pass a pointer to the first word in the RAM table (stack pointer slot 0). | ||
| 291 | /// Safety: Caller must ensure the RAM table is valid and aligned as required by the core. | ||
| 292 | pub unsafe fn vtor_set_ram_vector_base(base: *const u32) { | ||
| 293 | core::ptr::write_volatile(0xE000_ED08 as *mut u32, base as u32); | ||
| 294 | } | ||
| 295 | |||
| 296 | /// Install an interrupt handler into the current VTOR-based vector table. | ||
| 297 | /// This writes the function pointer at index 16 + irq number. | ||
| 298 | /// Safety: Caller must ensure VTOR points at a writable RAM table and that `handler` | ||
| 299 | /// has the correct ABI and lifetime. | ||
| 300 | pub unsafe fn install_irq_handler(irq: Interrupt, handler: unsafe extern "C" fn()) { | ||
| 301 | let vtor_base = core::ptr::read_volatile(0xE000_ED08 as *const u32) as *mut u32; | ||
| 302 | let idx = 16 + (irq as isize as usize); | ||
| 303 | core::ptr::write_volatile(vtor_base.add(idx), handler as usize as u32); | ||
| 304 | } | ||
| 305 | |||
| 306 | impl OsEvent { | ||
| 307 | /// Convenience to install the OS_EVENT handler into the RAM vector table. | ||
| 308 | /// Safety: See `install_irq_handler`. | ||
| 309 | pub unsafe fn install_handler(&self, handler: extern "C" fn()) { | ||
| 310 | install_irq_handler(Interrupt::OS_EVENT, handler); | ||
| 311 | } | ||
| 312 | } | ||
| 313 | |||
| 314 | /// Install OS_EVENT handler by raw address. Useful to avoid fn pointer type mismatches. | ||
| 315 | /// Safety: Caller must ensure the address is a valid `extern "C" fn()` handler. | ||
| 316 | pub unsafe fn os_event_install_handler_raw(handler_addr: usize) { | ||
| 317 | let vtor_base = core::ptr::read_volatile(0xE000_ED08 as *const u32) as *mut u32; | ||
| 318 | let idx = 16 + (Interrupt::OS_EVENT as isize as usize); | ||
| 319 | core::ptr::write_volatile(vtor_base.add(idx), handler_addr as u32); | ||
| 320 | } | ||
| 321 | |||
| 322 | /// Provide a conservative default IRQ handler that avoids wedging the system. | ||
| 323 | /// It clears all NVIC pending bits and returns, so spurious or reserved IRQs | ||
| 324 | /// don’t trap the core in an infinite WFI loop during bring-up. | ||
| 325 | #[no_mangle] | ||
| 326 | pub unsafe extern "C" fn DefaultHandler() { | ||
| 327 | let active = core::ptr::read_volatile(0xE000_ED04 as *const u32) & 0x1FF; | ||
| 328 | let cfsr = core::ptr::read_volatile(0xE000_ED28 as *const u32); | ||
| 329 | let hfsr = core::ptr::read_volatile(0xE000_ED2C as *const u32); | ||
| 330 | |||
| 331 | let sp = cortex_m::register::msp::read(); | ||
| 332 | let stacked = sp as *const u32; | ||
| 333 | // Stacked registers follow ARMv8-M procedure call standard order | ||
| 334 | let stacked_pc = unsafe { stacked.add(6).read() }; | ||
| 335 | let stacked_lr = unsafe { stacked.add(5).read() }; | ||
| 336 | |||
| 337 | LAST_DEFAULT_VECTOR.store(active as u16, Ordering::Relaxed); | ||
| 338 | LAST_DEFAULT_CFSR.store(cfsr, Ordering::Relaxed); | ||
| 339 | LAST_DEFAULT_HFSR.store(hfsr, Ordering::Relaxed); | ||
| 340 | LAST_DEFAULT_COUNT.fetch_add(1, Ordering::Relaxed); | ||
| 341 | LAST_DEFAULT_PC.store(stacked_pc, Ordering::Relaxed); | ||
| 342 | LAST_DEFAULT_LR.store(stacked_lr, Ordering::Relaxed); | ||
| 343 | LAST_DEFAULT_SP.store(sp, Ordering::Relaxed); | ||
| 344 | |||
| 345 | // Do nothing here: on some MCUs/TrustZone setups, writing NVIC from a spurious | ||
| 346 | // handler can fault if targeting the Secure bank. Just return. | ||
| 347 | cortex_m::asm::dsb(); | ||
| 348 | cortex_m::asm::isb(); | ||
| 349 | } | ||
diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 000000000..fe27aadba --- /dev/null +++ b/src/lib.rs | |||
| @@ -0,0 +1,142 @@ | |||
| 1 | #![no_std] | ||
| 2 | |||
| 3 | pub mod clocks; // still provide clock helpers | ||
| 4 | pub mod gpio; | ||
| 5 | pub mod pins; // pin mux helpers | ||
| 6 | pub mod reset; // reset control helpers | ||
| 7 | |||
| 8 | pub mod adc; | ||
| 9 | pub mod config; | ||
| 10 | pub mod interrupt; | ||
| 11 | pub mod lpuart; | ||
| 12 | pub mod ostimer; | ||
| 13 | pub mod rtc; | ||
| 14 | pub mod uart; | ||
| 15 | |||
| 16 | embassy_hal_internal::peripherals!(LPUART2, OSTIMER0, GPIO, RTC0, ADC1,); | ||
| 17 | |||
| 18 | /// Get access to the PAC Peripherals for low-level register access. | ||
| 19 | /// This is a lazy-initialized singleton that can be called after init(). | ||
| 20 | #[allow(static_mut_refs)] | ||
| 21 | pub fn pac() -> &'static pac::Peripherals { | ||
| 22 | // SAFETY: We only call this after init(), and the PAC is a singleton. | ||
| 23 | // The embassy peripheral tokens ensure we don't have multiple mutable accesses. | ||
| 24 | unsafe { | ||
| 25 | static mut PAC_INSTANCE: Option<pac::Peripherals> = None; | ||
| 26 | if PAC_INSTANCE.is_none() { | ||
| 27 | PAC_INSTANCE = Some(pac::Peripherals::steal()); | ||
| 28 | } | ||
| 29 | PAC_INSTANCE.as_ref().unwrap() | ||
| 30 | } | ||
| 31 | } | ||
| 32 | |||
| 33 | // Use cortex-m-rt's #[interrupt] attribute directly; PAC does not re-export it. | ||
| 34 | |||
| 35 | // Re-export interrupt traits and types | ||
| 36 | pub use adc::Adc1 as Adc1Token; | ||
| 37 | pub use gpio::pins::*; | ||
| 38 | pub use gpio::{AnyPin, Flex, Gpio as GpioToken, Input, Level, Output}; | ||
| 39 | pub use interrupt::InterruptExt; | ||
| 40 | #[cfg(feature = "unstable-pac")] | ||
| 41 | pub use mcxa_pac as pac; | ||
| 42 | #[cfg(not(feature = "unstable-pac"))] | ||
| 43 | pub(crate) use mcxa_pac as pac; | ||
| 44 | pub use ostimer::Ostimer0 as Ostimer0Token; | ||
| 45 | pub use rtc::Rtc0 as Rtc0Token; | ||
| 46 | pub use uart::Lpuart2 as Uart2Token; | ||
| 47 | |||
| 48 | /// Initialize HAL with configuration (mirrors embassy-imxrt style). Minimal: just take peripherals. | ||
| 49 | /// Also applies configurable NVIC priority for the OSTIMER OS_EVENT interrupt (no enabling). | ||
| 50 | #[allow(unused_variables)] | ||
| 51 | pub fn init(cfg: crate::config::Config) -> Peripherals { | ||
| 52 | let peripherals = Peripherals::take(); | ||
| 53 | // Apply user-configured priority early; enabling is left to examples/apps | ||
| 54 | crate::interrupt::OS_EVENT.set_priority(cfg.time_interrupt_priority); | ||
| 55 | // Apply user-configured priority early; enabling is left to examples/apps | ||
| 56 | crate::interrupt::RTC.set_priority(cfg.rtc_interrupt_priority); | ||
| 57 | // Apply user-configured priority early; enabling is left to examples/apps | ||
| 58 | crate::interrupt::ADC1.set_priority(cfg.adc_interrupt_priority); | ||
| 59 | peripherals | ||
| 60 | } | ||
| 61 | |||
| 62 | /// Optional hook called by cortex-m-rt before RAM init. | ||
| 63 | /// We proactively mask and clear all NVIC IRQs to avoid wedges from stale state | ||
| 64 | /// left by soft resets/debug sessions. | ||
| 65 | /// | ||
| 66 | /// NOTE: Manual VTOR setup is required for RAM execution. The cortex-m-rt 'set-vtor' | ||
| 67 | /// feature is incompatible with our setup because it expects __vector_table to be | ||
| 68 | /// defined differently than how our RAM-based linker script arranges it. | ||
| 69 | #[no_mangle] | ||
| 70 | pub unsafe extern "C" fn __pre_init() { | ||
| 71 | // Set the VTOR to point to the interrupt vector table in RAM | ||
| 72 | // This is required since code runs from RAM on this MCU | ||
| 73 | crate::interrupt::vtor_set_ram_vector_base(0x2000_0000 as *const u32); | ||
| 74 | |||
| 75 | // Mask and clear pending for all NVIC lines (0..127) to avoid stale state across runs. | ||
| 76 | let nvic = &*cortex_m::peripheral::NVIC::PTR; | ||
| 77 | for i in 0..4 { | ||
| 78 | // 4 words x 32 = 128 IRQs | ||
| 79 | nvic.icer[i].write(0xFFFF_FFFF); | ||
| 80 | nvic.icpr[i].write(0xFFFF_FFFF); | ||
| 81 | } | ||
| 82 | // Do NOT touch peripheral registers here: clocks may be off and accesses can fault. | ||
| 83 | crate::interrupt::clear_default_handler_snapshot(); | ||
| 84 | } | ||
| 85 | |||
| 86 | /// Internal helper to dispatch a type-level interrupt handler. | ||
| 87 | #[inline(always)] | ||
| 88 | #[doc(hidden)] | ||
| 89 | pub unsafe fn __handle_interrupt<T, H>() | ||
| 90 | where | ||
| 91 | T: crate::interrupt::typelevel::Interrupt, | ||
| 92 | H: crate::interrupt::typelevel::Handler<T>, | ||
| 93 | { | ||
| 94 | H::on_interrupt(); | ||
| 95 | } | ||
| 96 | |||
| 97 | /// Macro to bind interrupts to handlers, similar to embassy-imxrt. | ||
| 98 | /// | ||
| 99 | /// Example: | ||
| 100 | /// - Bind OS_EVENT to the OSTIMER time-driver handler | ||
| 101 | /// bind_interrupts!(struct Irqs { OS_EVENT => crate::ostimer::time_driver::OsEventHandler; }); | ||
| 102 | #[macro_export] | ||
| 103 | macro_rules! bind_interrupts { | ||
| 104 | ($(#[$attr:meta])* $vis:vis struct $name:ident { | ||
| 105 | $( | ||
| 106 | $(#[cfg($cond_irq:meta)])? | ||
| 107 | $irq:ident => $( | ||
| 108 | $(#[cfg($cond_handler:meta)])? | ||
| 109 | $handler:ty | ||
| 110 | ),*; | ||
| 111 | )* | ||
| 112 | }) => { | ||
| 113 | #[derive(Copy, Clone)] | ||
| 114 | $(#[$attr])* | ||
| 115 | $vis struct $name; | ||
| 116 | |||
| 117 | $( | ||
| 118 | #[allow(non_snake_case)] | ||
| 119 | #[no_mangle] | ||
| 120 | $(#[cfg($cond_irq)])? | ||
| 121 | unsafe extern "C" fn $irq() { | ||
| 122 | unsafe { | ||
| 123 | $( | ||
| 124 | $(#[cfg($cond_handler)])? | ||
| 125 | <$handler as $crate::interrupt::typelevel::Handler<$crate::interrupt::typelevel::$irq>>::on_interrupt(); | ||
| 126 | )* | ||
| 127 | } | ||
| 128 | } | ||
| 129 | |||
| 130 | $(#[cfg($cond_irq)])? | ||
| 131 | $crate::bind_interrupts!(@inner | ||
| 132 | $( | ||
| 133 | $(#[cfg($cond_handler)])? | ||
| 134 | unsafe impl $crate::interrupt::typelevel::Binding<$crate::interrupt::typelevel::$irq, $handler> for $name {} | ||
| 135 | )* | ||
| 136 | ); | ||
| 137 | )* | ||
| 138 | }; | ||
| 139 | (@inner $($t:tt)*) => { | ||
| 140 | $($t)* | ||
| 141 | } | ||
| 142 | } | ||
diff --git a/src/lpuart/buffered.rs b/src/lpuart/buffered.rs new file mode 100644 index 000000000..0413fed8e --- /dev/null +++ b/src/lpuart/buffered.rs | |||
| @@ -0,0 +1,675 @@ | |||
| 1 | use core::future::poll_fn; | ||
| 2 | use core::marker::PhantomData; | ||
| 3 | use core::sync::atomic::{AtomicBool, Ordering}; | ||
| 4 | use core::task::Poll; | ||
| 5 | |||
| 6 | use embassy_hal_internal::atomic_ring_buffer::RingBuffer; | ||
| 7 | use embassy_hal_internal::Peri; | ||
| 8 | use embassy_sync::waitqueue::AtomicWaker; | ||
| 9 | |||
| 10 | use super::*; | ||
| 11 | use crate::interrupt; | ||
| 12 | |||
| 13 | // ============================================================================ | ||
| 14 | // STATIC STATE MANAGEMENT | ||
| 15 | // ============================================================================ | ||
| 16 | |||
| 17 | /// State for buffered LPUART operations | ||
| 18 | pub struct State { | ||
| 19 | rx_waker: AtomicWaker, | ||
| 20 | rx_buf: RingBuffer, | ||
| 21 | tx_waker: AtomicWaker, | ||
| 22 | tx_buf: RingBuffer, | ||
| 23 | tx_done: AtomicBool, | ||
| 24 | initialized: AtomicBool, | ||
| 25 | } | ||
| 26 | |||
| 27 | impl State { | ||
| 28 | /// Create a new state instance | ||
| 29 | pub const fn new() -> Self { | ||
| 30 | Self { | ||
| 31 | rx_waker: AtomicWaker::new(), | ||
| 32 | rx_buf: RingBuffer::new(), | ||
| 33 | tx_waker: AtomicWaker::new(), | ||
| 34 | tx_buf: RingBuffer::new(), | ||
| 35 | tx_done: AtomicBool::new(true), | ||
| 36 | initialized: AtomicBool::new(false), | ||
| 37 | } | ||
| 38 | } | ||
| 39 | } | ||
| 40 | // ============================================================================ | ||
| 41 | // BUFFERED DRIVER STRUCTURES | ||
| 42 | // ============================================================================ | ||
| 43 | |||
| 44 | /// Buffered LPUART driver | ||
| 45 | pub struct BufferedLpuart<'a> { | ||
| 46 | tx: BufferedLpuartTx<'a>, | ||
| 47 | rx: BufferedLpuartRx<'a>, | ||
| 48 | } | ||
| 49 | |||
| 50 | /// Buffered LPUART TX driver | ||
| 51 | pub struct BufferedLpuartTx<'a> { | ||
| 52 | info: Info, | ||
| 53 | state: &'static State, | ||
| 54 | _tx_pin: Peri<'a, AnyPin>, | ||
| 55 | } | ||
| 56 | |||
| 57 | /// Buffered LPUART RX driver | ||
| 58 | pub struct BufferedLpuartRx<'a> { | ||
| 59 | info: Info, | ||
| 60 | state: &'static State, | ||
| 61 | _rx_pin: Peri<'a, AnyPin>, | ||
| 62 | } | ||
| 63 | |||
| 64 | // ============================================================================ | ||
| 65 | // BUFFERED LPUART IMPLEMENTATION | ||
| 66 | // ============================================================================ | ||
| 67 | |||
| 68 | impl<'a> BufferedLpuart<'a> { | ||
| 69 | /// Create a new buffered LPUART instance | ||
| 70 | pub fn new<T: Instance>( | ||
| 71 | _inner: Peri<'a, T>, | ||
| 72 | tx_pin: Peri<'a, impl TxPin<T>>, | ||
| 73 | rx_pin: Peri<'a, impl RxPin<T>>, | ||
| 74 | _irq: impl interrupt::typelevel::Binding<T::Interrupt, BufferedInterruptHandler<T>> + 'a, | ||
| 75 | tx_buffer: &'a mut [u8], | ||
| 76 | rx_buffer: &'a mut [u8], | ||
| 77 | config: Config, | ||
| 78 | ) -> Result<Self> { | ||
| 79 | // Configure pins | ||
| 80 | tx_pin.as_tx(); | ||
| 81 | rx_pin.as_rx(); | ||
| 82 | |||
| 83 | // Convert pins to AnyPin | ||
| 84 | let tx_pin: Peri<'a, AnyPin> = tx_pin.into(); | ||
| 85 | let rx_pin: Peri<'a, AnyPin> = rx_pin.into(); | ||
| 86 | |||
| 87 | let state = T::buffered_state(); | ||
| 88 | |||
| 89 | // Initialize the peripheral | ||
| 90 | Self::init::<T>(Some(&tx_pin), Some(&rx_pin), None, None, tx_buffer, rx_buffer, config)?; | ||
| 91 | |||
| 92 | Ok(Self { | ||
| 93 | tx: BufferedLpuartTx { | ||
| 94 | info: T::info(), | ||
| 95 | state, | ||
| 96 | _tx_pin: tx_pin, | ||
| 97 | }, | ||
| 98 | rx: BufferedLpuartRx { | ||
| 99 | info: T::info(), | ||
| 100 | state, | ||
| 101 | _rx_pin: rx_pin, | ||
| 102 | }, | ||
| 103 | }) | ||
| 104 | } | ||
| 105 | |||
| 106 | /// Create a new buffered LPUART with flexible pin configuration | ||
| 107 | pub fn new_with_pins<T: Instance>( | ||
| 108 | _inner: Peri<'a, T>, | ||
| 109 | tx_pin: Option<Peri<'a, impl TxPin<T>>>, | ||
| 110 | rx_pin: Option<Peri<'a, impl RxPin<T>>>, | ||
| 111 | rts_pin: Option<Peri<'a, impl RtsPin<T>>>, | ||
| 112 | cts_pin: Option<Peri<'a, impl CtsPin<T>>>, | ||
| 113 | _irq: impl interrupt::typelevel::Binding<T::Interrupt, BufferedInterruptHandler<T>> + 'a, | ||
| 114 | tx_buffer: &'a mut [u8], | ||
| 115 | rx_buffer: &'a mut [u8], | ||
| 116 | config: Config, | ||
| 117 | ) -> Result<Self> { | ||
| 118 | // Configure pins if provided | ||
| 119 | let tx_pin = tx_pin.map(|pin| { | ||
| 120 | pin.as_tx(); | ||
| 121 | let converted: Peri<'a, AnyPin> = pin.into(); | ||
| 122 | converted | ||
| 123 | }); | ||
| 124 | |||
| 125 | let rx_pin = rx_pin.map(|pin| { | ||
| 126 | pin.as_rx(); | ||
| 127 | let converted: Peri<'a, AnyPin> = pin.into(); | ||
| 128 | converted | ||
| 129 | }); | ||
| 130 | |||
| 131 | let rts_pin = rts_pin.map(|pin| { | ||
| 132 | pin.as_rts(); | ||
| 133 | let converted: Peri<'a, AnyPin> = pin.into(); | ||
| 134 | converted | ||
| 135 | }); | ||
| 136 | |||
| 137 | let cts_pin = cts_pin.map(|pin| { | ||
| 138 | pin.as_cts(); | ||
| 139 | let converted: Peri<'a, AnyPin> = pin.into(); | ||
| 140 | converted | ||
| 141 | }); | ||
| 142 | |||
| 143 | let state = T::buffered_state(); | ||
| 144 | |||
| 145 | // Initialize the peripheral | ||
| 146 | Self::init::<T>( | ||
| 147 | tx_pin.as_ref(), | ||
| 148 | rx_pin.as_ref(), | ||
| 149 | rts_pin.as_ref(), | ||
| 150 | cts_pin.as_ref(), | ||
| 151 | tx_buffer, | ||
| 152 | rx_buffer, | ||
| 153 | config, | ||
| 154 | )?; | ||
| 155 | |||
| 156 | // Create TX and RX instances | ||
| 157 | let (tx, rx) = if let (Some(tx_pin), Some(rx_pin)) = (tx_pin, rx_pin) { | ||
| 158 | ( | ||
| 159 | BufferedLpuartTx { | ||
| 160 | info: T::info(), | ||
| 161 | state, | ||
| 162 | _tx_pin: tx_pin, | ||
| 163 | }, | ||
| 164 | BufferedLpuartRx { | ||
| 165 | info: T::info(), | ||
| 166 | state, | ||
| 167 | _rx_pin: rx_pin, | ||
| 168 | }, | ||
| 169 | ) | ||
| 170 | } else { | ||
| 171 | return Err(Error::InvalidArgument); | ||
| 172 | }; | ||
| 173 | |||
| 174 | Ok(Self { tx, rx }) | ||
| 175 | } | ||
| 176 | |||
| 177 | fn init<T: Instance>( | ||
| 178 | _tx: Option<&Peri<'a, AnyPin>>, | ||
| 179 | _rx: Option<&Peri<'a, AnyPin>>, | ||
| 180 | _rts: Option<&Peri<'a, AnyPin>>, | ||
| 181 | _cts: Option<&Peri<'a, AnyPin>>, | ||
| 182 | tx_buffer: &'a mut [u8], | ||
| 183 | rx_buffer: &'a mut [u8], | ||
| 184 | mut config: Config, | ||
| 185 | ) -> Result<()> { | ||
| 186 | let regs = T::info().regs; | ||
| 187 | let state = T::buffered_state(); | ||
| 188 | |||
| 189 | // Check if already initialized | ||
| 190 | if state.initialized.load(Ordering::Relaxed) { | ||
| 191 | return Err(Error::InvalidArgument); | ||
| 192 | } | ||
| 193 | |||
| 194 | // Initialize ring buffers | ||
| 195 | assert!(!tx_buffer.is_empty()); | ||
| 196 | unsafe { state.tx_buf.init(tx_buffer.as_mut_ptr(), tx_buffer.len()) } | ||
| 197 | |||
| 198 | assert!(!rx_buffer.is_empty()); | ||
| 199 | unsafe { state.rx_buf.init(rx_buffer.as_mut_ptr(), rx_buffer.len()) } | ||
| 200 | |||
| 201 | // Mark as initialized | ||
| 202 | state.initialized.store(true, Ordering::Relaxed); | ||
| 203 | |||
| 204 | // Enable TX and RX for buffered operation | ||
| 205 | config.enable_tx = true; | ||
| 206 | config.enable_rx = true; | ||
| 207 | |||
| 208 | // Perform standard initialization | ||
| 209 | perform_software_reset(regs); | ||
| 210 | disable_transceiver(regs); | ||
| 211 | configure_baudrate(regs, config.baudrate_bps, config.clock)?; | ||
| 212 | configure_frame_format(regs, &config); | ||
| 213 | configure_control_settings(regs, &config); | ||
| 214 | configure_fifo(regs, &config); | ||
| 215 | clear_all_status_flags(regs); | ||
| 216 | configure_flow_control(regs, &config); | ||
| 217 | configure_bit_order(regs, config.msb_firs); | ||
| 218 | |||
| 219 | // Enable interrupts for buffered operation | ||
| 220 | cortex_m::interrupt::free(|_| { | ||
| 221 | regs.ctrl().modify(|_, w| { | ||
| 222 | w.rie() | ||
| 223 | .enabled() // RX interrupt | ||
| 224 | .orie() | ||
| 225 | .enabled() // Overrun interrupt | ||
| 226 | .peie() | ||
| 227 | .enabled() // Parity error interrupt | ||
| 228 | .feie() | ||
| 229 | .enabled() // Framing error interrupt | ||
| 230 | .neie() | ||
| 231 | .enabled() // Noise error interrupt | ||
| 232 | }); | ||
| 233 | }); | ||
| 234 | |||
| 235 | // Enable the transceiver | ||
| 236 | enable_transceiver(regs, config.enable_tx, config.enable_rx); | ||
| 237 | |||
| 238 | // Enable the interrupt | ||
| 239 | // unsafe { | ||
| 240 | // // TODO: Used the propper interrupt enable method for the specific LPUART instance | ||
| 241 | // // T::Interrupt::enable(); | ||
| 242 | // } | ||
| 243 | |||
| 244 | Ok(()) | ||
| 245 | } | ||
| 246 | |||
| 247 | /// Split the buffered LPUART into separate TX and RX parts | ||
| 248 | pub fn split(self) -> (BufferedLpuartTx<'a>, BufferedLpuartRx<'a>) { | ||
| 249 | (self.tx, self.rx) | ||
| 250 | } | ||
| 251 | |||
| 252 | /// Get mutable references to TX and RX parts | ||
| 253 | pub fn split_ref(&mut self) -> (&mut BufferedLpuartTx<'a>, &mut BufferedLpuartRx<'a>) { | ||
| 254 | (&mut self.tx, &mut self.rx) | ||
| 255 | } | ||
| 256 | } | ||
| 257 | |||
| 258 | // ============================================================================ | ||
| 259 | // BUFFERED TX IMPLEMENTATION | ||
| 260 | // ============================================================================ | ||
| 261 | |||
| 262 | impl<'a> BufferedLpuartTx<'a> { | ||
| 263 | /// Create a new TX-only buffered LPUART | ||
| 264 | pub fn new<T: Instance>( | ||
| 265 | _inner: Peri<'a, T>, | ||
| 266 | tx_pin: Peri<'a, impl TxPin<T>>, | ||
| 267 | _irq: impl interrupt::typelevel::Binding<T::Interrupt, BufferedInterruptHandler<T>> + 'a, | ||
| 268 | tx_buffer: &'a mut [u8], | ||
| 269 | config: Config, | ||
| 270 | ) -> Result<Self> { | ||
| 271 | tx_pin.as_tx(); | ||
| 272 | let tx_pin: Peri<'a, AnyPin> = tx_pin.into(); | ||
| 273 | |||
| 274 | let info = T::info(); | ||
| 275 | let state = T::buffered_state(); | ||
| 276 | |||
| 277 | // Check if already initialized | ||
| 278 | if state.initialized.load(Ordering::Relaxed) { | ||
| 279 | return Err(Error::InvalidArgument); | ||
| 280 | } | ||
| 281 | |||
| 282 | // Initialize TX ring buffer only | ||
| 283 | unsafe { | ||
| 284 | let tx_buf = &state.tx_buf as *const _ as *mut RingBuffer; | ||
| 285 | (*tx_buf).init(tx_buffer.as_mut_ptr(), tx_buffer.len()); | ||
| 286 | } | ||
| 287 | |||
| 288 | state.initialized.store(true, Ordering::Relaxed); | ||
| 289 | |||
| 290 | // Initialize with TX only | ||
| 291 | BufferedLpuart::init::<T>( | ||
| 292 | Some(&tx_pin), | ||
| 293 | None, | ||
| 294 | None, | ||
| 295 | None, | ||
| 296 | tx_buffer, | ||
| 297 | &mut [], // Empty RX buffer | ||
| 298 | config, | ||
| 299 | )?; | ||
| 300 | |||
| 301 | Ok(Self { | ||
| 302 | info, | ||
| 303 | state, | ||
| 304 | _tx_pin: tx_pin, | ||
| 305 | }) | ||
| 306 | } | ||
| 307 | |||
| 308 | /// Write data asynchronously | ||
| 309 | pub async fn write(&mut self, buf: &[u8]) -> Result<usize> { | ||
| 310 | let mut written = 0; | ||
| 311 | |||
| 312 | for &byte in buf { | ||
| 313 | // Wait for space in the buffer | ||
| 314 | poll_fn(|cx| { | ||
| 315 | self.state.tx_waker.register(cx.waker()); | ||
| 316 | |||
| 317 | let mut writer = unsafe { self.state.tx_buf.writer() }; | ||
| 318 | if writer.push_one(byte) { | ||
| 319 | // Enable TX interrupt to start transmission | ||
| 320 | cortex_m::interrupt::free(|_| { | ||
| 321 | self.info.regs.ctrl().modify(|_, w| w.tie().enabled()); | ||
| 322 | }); | ||
| 323 | Poll::Ready(Ok(())) | ||
| 324 | } else { | ||
| 325 | Poll::Pending | ||
| 326 | } | ||
| 327 | }) | ||
| 328 | .await?; | ||
| 329 | |||
| 330 | written += 1; | ||
| 331 | } | ||
| 332 | |||
| 333 | Ok(written) | ||
| 334 | } | ||
| 335 | |||
| 336 | /// Flush the TX buffer and wait for transmission to complete | ||
| 337 | pub async fn flush(&mut self) -> Result<()> { | ||
| 338 | // Wait for TX buffer to empty and transmission to complete | ||
| 339 | poll_fn(|cx| { | ||
| 340 | self.state.tx_waker.register(cx.waker()); | ||
| 341 | |||
| 342 | let tx_empty = self.state.tx_buf.is_empty(); | ||
| 343 | let fifo_empty = self.info.regs.water().read().txcount().bits() == 0; | ||
| 344 | let tc_complete = self.info.regs.stat().read().tc().is_complete(); | ||
| 345 | |||
| 346 | if tx_empty && fifo_empty && tc_complete { | ||
| 347 | Poll::Ready(Ok(())) | ||
| 348 | } else { | ||
| 349 | // Enable appropriate interrupt | ||
| 350 | cortex_m::interrupt::free(|_| { | ||
| 351 | if !tx_empty { | ||
| 352 | self.info.regs.ctrl().modify(|_, w| w.tie().enabled()); | ||
| 353 | } else { | ||
| 354 | self.info.regs.ctrl().modify(|_, w| w.tcie().enabled()); | ||
| 355 | } | ||
| 356 | }); | ||
| 357 | Poll::Pending | ||
| 358 | } | ||
| 359 | }) | ||
| 360 | .await | ||
| 361 | } | ||
| 362 | |||
| 363 | /// Try to write without blocking | ||
| 364 | pub fn try_write(&mut self, buf: &[u8]) -> Result<usize> { | ||
| 365 | let mut writer = unsafe { self.state.tx_buf.writer() }; | ||
| 366 | let mut written = 0; | ||
| 367 | |||
| 368 | for &byte in buf { | ||
| 369 | if writer.push_one(byte) { | ||
| 370 | written += 1; | ||
| 371 | } else { | ||
| 372 | break; | ||
| 373 | } | ||
| 374 | } | ||
| 375 | |||
| 376 | if written > 0 { | ||
| 377 | // Enable TX interrupt to start transmission | ||
| 378 | cortex_m::interrupt::free(|_| { | ||
| 379 | self.info.regs.ctrl().modify(|_, w| w.tie().enabled()); | ||
| 380 | }); | ||
| 381 | } | ||
| 382 | |||
| 383 | Ok(written) | ||
| 384 | } | ||
| 385 | } | ||
| 386 | |||
| 387 | // ============================================================================ | ||
| 388 | // BUFFERED RX IMPLEMENTATION | ||
| 389 | // ============================================================================ | ||
| 390 | |||
| 391 | impl<'a> BufferedLpuartRx<'a> { | ||
| 392 | /// Create a new RX-only buffered LPUART | ||
| 393 | pub fn new<T: Instance>( | ||
| 394 | _inner: Peri<'a, T>, | ||
| 395 | rx_pin: Peri<'a, impl RxPin<T>>, | ||
| 396 | _irq: impl interrupt::typelevel::Binding<T::Interrupt, BufferedInterruptHandler<T>> + 'a, | ||
| 397 | rx_buffer: &'a mut [u8], | ||
| 398 | config: Config, | ||
| 399 | ) -> Result<Self> { | ||
| 400 | rx_pin.as_rx(); | ||
| 401 | let rx_pin: Peri<'a, AnyPin> = rx_pin.into(); | ||
| 402 | |||
| 403 | let info = T::info(); | ||
| 404 | let state = T::buffered_state(); | ||
| 405 | |||
| 406 | // Check if already initialized | ||
| 407 | if state.initialized.load(Ordering::Relaxed) { | ||
| 408 | return Err(Error::InvalidArgument); | ||
| 409 | } | ||
| 410 | |||
| 411 | // Initialize RX ring buffer only | ||
| 412 | unsafe { | ||
| 413 | let rx_buf = &state.rx_buf as *const _ as *mut RingBuffer; | ||
| 414 | (*rx_buf).init(rx_buffer.as_mut_ptr(), rx_buffer.len()); | ||
| 415 | } | ||
| 416 | |||
| 417 | state.initialized.store(true, Ordering::Relaxed); | ||
| 418 | |||
| 419 | // Initialize with RX only | ||
| 420 | BufferedLpuart::init::<T>( | ||
| 421 | None, | ||
| 422 | Some(&rx_pin), | ||
| 423 | None, | ||
| 424 | None, | ||
| 425 | &mut [], // Empty TX buffer | ||
| 426 | rx_buffer, | ||
| 427 | config, | ||
| 428 | )?; | ||
| 429 | |||
| 430 | Ok(Self { | ||
| 431 | info, | ||
| 432 | state, | ||
| 433 | _rx_pin: rx_pin, | ||
| 434 | }) | ||
| 435 | } | ||
| 436 | |||
| 437 | /// Read data asynchronously | ||
| 438 | pub async fn read(&mut self, buf: &mut [u8]) -> Result<usize> { | ||
| 439 | if buf.is_empty() { | ||
| 440 | return Ok(0); | ||
| 441 | } | ||
| 442 | |||
| 443 | let mut read = 0; | ||
| 444 | |||
| 445 | // Try to read available data | ||
| 446 | poll_fn(|cx| { | ||
| 447 | self.state.rx_waker.register(cx.waker()); | ||
| 448 | |||
| 449 | // Disable RX interrupt while reading from buffer | ||
| 450 | cortex_m::interrupt::free(|_| { | ||
| 451 | self.info.regs.ctrl().modify(|_, w| w.rie().disabled()); | ||
| 452 | }); | ||
| 453 | |||
| 454 | let mut reader = unsafe { self.state.rx_buf.reader() }; | ||
| 455 | let available = reader.pop(|data| { | ||
| 456 | let to_copy = core::cmp::min(data.len(), buf.len() - read); | ||
| 457 | if to_copy > 0 { | ||
| 458 | buf[read..read + to_copy].copy_from_slice(&data[..to_copy]); | ||
| 459 | read += to_copy; | ||
| 460 | } | ||
| 461 | to_copy | ||
| 462 | }); | ||
| 463 | |||
| 464 | // Re-enable RX interrupt | ||
| 465 | cortex_m::interrupt::free(|_| { | ||
| 466 | self.info.regs.ctrl().modify(|_, w| w.rie().enabled()); | ||
| 467 | }); | ||
| 468 | |||
| 469 | if read > 0 { | ||
| 470 | Poll::Ready(Ok(read)) | ||
| 471 | } else if available == 0 { | ||
| 472 | Poll::Pending | ||
| 473 | } else { | ||
| 474 | Poll::Ready(Ok(0)) | ||
| 475 | } | ||
| 476 | }) | ||
| 477 | .await | ||
| 478 | } | ||
| 479 | |||
| 480 | /// Try to read without blocking | ||
| 481 | pub fn try_read(&mut self, buf: &mut [u8]) -> Result<usize> { | ||
| 482 | if buf.is_empty() { | ||
| 483 | return Ok(0); | ||
| 484 | } | ||
| 485 | |||
| 486 | // Disable RX interrupt while reading from buffer | ||
| 487 | cortex_m::interrupt::free(|_| { | ||
| 488 | self.info.regs.ctrl().modify(|_, w| w.rie().disabled()); | ||
| 489 | }); | ||
| 490 | |||
| 491 | let mut reader = unsafe { self.state.rx_buf.reader() }; | ||
| 492 | let read = reader.pop(|data| { | ||
| 493 | let to_copy = core::cmp::min(data.len(), buf.len()); | ||
| 494 | if to_copy > 0 { | ||
| 495 | buf[..to_copy].copy_from_slice(&data[..to_copy]); | ||
| 496 | } | ||
| 497 | to_copy | ||
| 498 | }); | ||
| 499 | |||
| 500 | // Re-enable RX interrupt | ||
| 501 | cortex_m::interrupt::free(|_| { | ||
| 502 | self.info.regs.ctrl().modify(|_, w| w.rie().enabled()); | ||
| 503 | }); | ||
| 504 | |||
| 505 | Ok(read) | ||
| 506 | } | ||
| 507 | } | ||
| 508 | |||
| 509 | // ============================================================================ | ||
| 510 | // INTERRUPT HANDLER | ||
| 511 | // ============================================================================ | ||
| 512 | |||
| 513 | /// Buffered UART interrupt handler | ||
| 514 | pub struct BufferedInterruptHandler<T: Instance> { | ||
| 515 | _phantom: PhantomData<T>, | ||
| 516 | } | ||
| 517 | |||
| 518 | impl<T: Instance> crate::interrupt::typelevel::Handler<T::Interrupt> for BufferedInterruptHandler<T> { | ||
| 519 | unsafe fn on_interrupt() { | ||
| 520 | let regs = T::info().regs; | ||
| 521 | let state = T::buffered_state(); | ||
| 522 | |||
| 523 | // Check if this instance is initialized | ||
| 524 | if !state.initialized.load(Ordering::Relaxed) { | ||
| 525 | return; | ||
| 526 | } | ||
| 527 | |||
| 528 | let ctrl = regs.ctrl().read(); | ||
| 529 | let stat = regs.stat().read(); | ||
| 530 | let has_fifo = regs.param().read().rxfifo().bits() > 0; | ||
| 531 | |||
| 532 | // Handle overrun error | ||
| 533 | if stat.or().is_overrun() { | ||
| 534 | regs.stat().write(|w| w.or().clear_bit_by_one()); | ||
| 535 | state.rx_waker.wake(); | ||
| 536 | return; | ||
| 537 | } | ||
| 538 | |||
| 539 | // Clear other error flags | ||
| 540 | if stat.pf().is_parity() { | ||
| 541 | regs.stat().write(|w| w.pf().clear_bit_by_one()); | ||
| 542 | } | ||
| 543 | if stat.fe().is_error() { | ||
| 544 | regs.stat().write(|w| w.fe().clear_bit_by_one()); | ||
| 545 | } | ||
| 546 | if stat.nf().is_noise() { | ||
| 547 | regs.stat().write(|w| w.nf().clear_bit_by_one()); | ||
| 548 | } | ||
| 549 | |||
| 550 | // Handle RX data | ||
| 551 | if ctrl.rie().is_enabled() && (has_data(regs) || stat.idle().is_idle()) { | ||
| 552 | let mut pushed_any = false; | ||
| 553 | let mut writer = state.rx_buf.writer(); | ||
| 554 | |||
| 555 | if has_fifo { | ||
| 556 | // Read from FIFO | ||
| 557 | while regs.water().read().rxcount().bits() > 0 { | ||
| 558 | let byte = (regs.data().read().bits() & 0xFF) as u8; | ||
| 559 | if writer.push_one(byte) { | ||
| 560 | pushed_any = true; | ||
| 561 | } else { | ||
| 562 | // Buffer full, stop reading | ||
| 563 | break; | ||
| 564 | } | ||
| 565 | } | ||
| 566 | } else { | ||
| 567 | // Read single byte | ||
| 568 | if regs.stat().read().rdrf().is_rxdata() { | ||
| 569 | let byte = (regs.data().read().bits() & 0xFF) as u8; | ||
| 570 | if writer.push_one(byte) { | ||
| 571 | pushed_any = true; | ||
| 572 | } | ||
| 573 | } | ||
| 574 | } | ||
| 575 | |||
| 576 | if pushed_any { | ||
| 577 | state.rx_waker.wake(); | ||
| 578 | } | ||
| 579 | |||
| 580 | // Clear idle flag if set | ||
| 581 | if stat.idle().is_idle() { | ||
| 582 | regs.stat().write(|w| w.idle().clear_bit_by_one()); | ||
| 583 | } | ||
| 584 | } | ||
| 585 | |||
| 586 | // Handle TX data | ||
| 587 | if ctrl.tie().is_enabled() { | ||
| 588 | let mut sent_any = false; | ||
| 589 | let mut reader = state.tx_buf.reader(); | ||
| 590 | |||
| 591 | // Send data while TX buffer is ready and we have data | ||
| 592 | while regs.stat().read().tdre().is_no_txdata() { | ||
| 593 | if let Some(byte) = reader.pop_one() { | ||
| 594 | regs.data().write(|w| w.bits(u32::from(byte))); | ||
| 595 | sent_any = true; | ||
| 596 | } else { | ||
| 597 | // No more data to send | ||
| 598 | break; | ||
| 599 | } | ||
| 600 | } | ||
| 601 | |||
| 602 | if sent_any { | ||
| 603 | state.tx_waker.wake(); | ||
| 604 | } | ||
| 605 | |||
| 606 | // If buffer is empty, switch to TC interrupt or disable | ||
| 607 | if state.tx_buf.is_empty() { | ||
| 608 | cortex_m::interrupt::free(|_| { | ||
| 609 | regs.ctrl().modify(|_, w| w.tie().disabled().tcie().enabled()); | ||
| 610 | }); | ||
| 611 | } | ||
| 612 | } | ||
| 613 | |||
| 614 | // Handle transmission complete | ||
| 615 | if ctrl.tcie().is_enabled() { | ||
| 616 | if regs.stat().read().tc().is_complete() { | ||
| 617 | state.tx_done.store(true, Ordering::Release); | ||
| 618 | state.tx_waker.wake(); | ||
| 619 | |||
| 620 | // Disable TC interrupt | ||
| 621 | cortex_m::interrupt::free(|_| { | ||
| 622 | regs.ctrl().modify(|_, w| w.tcie().disabled()); | ||
| 623 | }); | ||
| 624 | } | ||
| 625 | } | ||
| 626 | } | ||
| 627 | } | ||
| 628 | |||
| 629 | // ============================================================================ | ||
| 630 | // EMBEDDED-IO ASYNC TRAIT IMPLEMENTATIONS | ||
| 631 | // ============================================================================ | ||
| 632 | |||
| 633 | impl embedded_io_async::ErrorType for BufferedLpuartTx<'_> { | ||
| 634 | type Error = Error; | ||
| 635 | } | ||
| 636 | |||
| 637 | impl embedded_io_async::ErrorType for BufferedLpuartRx<'_> { | ||
| 638 | type Error = Error; | ||
| 639 | } | ||
| 640 | |||
| 641 | impl embedded_io_async::ErrorType for BufferedLpuart<'_> { | ||
| 642 | type Error = Error; | ||
| 643 | } | ||
| 644 | |||
| 645 | impl embedded_io_async::Write for BufferedLpuartTx<'_> { | ||
| 646 | async fn write(&mut self, buf: &[u8]) -> core::result::Result<usize, Self::Error> { | ||
| 647 | self.write(buf).await | ||
| 648 | } | ||
| 649 | |||
| 650 | async fn flush(&mut self) -> core::result::Result<(), Self::Error> { | ||
| 651 | self.flush().await | ||
| 652 | } | ||
| 653 | } | ||
| 654 | |||
| 655 | impl embedded_io_async::Read for BufferedLpuartRx<'_> { | ||
| 656 | async fn read(&mut self, buf: &mut [u8]) -> core::result::Result<usize, Self::Error> { | ||
| 657 | self.read(buf).await | ||
| 658 | } | ||
| 659 | } | ||
| 660 | |||
| 661 | impl embedded_io_async::Write for BufferedLpuart<'_> { | ||
| 662 | async fn write(&mut self, buf: &[u8]) -> core::result::Result<usize, Self::Error> { | ||
| 663 | self.tx.write(buf).await | ||
| 664 | } | ||
| 665 | |||
| 666 | async fn flush(&mut self) -> core::result::Result<(), Self::Error> { | ||
| 667 | self.tx.flush().await | ||
| 668 | } | ||
| 669 | } | ||
| 670 | |||
| 671 | impl embedded_io_async::Read for BufferedLpuart<'_> { | ||
| 672 | async fn read(&mut self, buf: &mut [u8]) -> core::result::Result<usize, Self::Error> { | ||
| 673 | self.rx.read(buf).await | ||
| 674 | } | ||
| 675 | } | ||
diff --git a/src/lpuart/mod.rs b/src/lpuart/mod.rs new file mode 100644 index 000000000..bed10bdb0 --- /dev/null +++ b/src/lpuart/mod.rs | |||
| @@ -0,0 +1,1201 @@ | |||
| 1 | use core::marker::PhantomData; | ||
| 2 | |||
| 3 | use embassy_hal_internal::{Peri, PeripheralType}; | ||
| 4 | use paste::paste; | ||
| 5 | |||
| 6 | use crate::pac::lpuart0::baud::Sbns as StopBits; | ||
| 7 | use crate::pac::lpuart0::ctrl::{Idlecfg as IdleConfig, Ilt as IdleType, Pt as Parity, M as DataBits}; | ||
| 8 | use crate::pac::lpuart0::modir::{Txctsc as TxCtsConfig, Txctssrc as TxCtsSource}; | ||
| 9 | use crate::pac::lpuart0::stat::Msbf as MsbFirst; | ||
| 10 | use crate::{interrupt, pac}; | ||
| 11 | |||
| 12 | pub mod buffered; | ||
| 13 | |||
| 14 | // ============================================================================ | ||
| 15 | // STUB IMPLEMENTATION | ||
| 16 | // ============================================================================ | ||
| 17 | |||
| 18 | // Stub implementation for LIB (Peripherals), GPIO, DMA and CLOCK until stable API | ||
| 19 | // Pin and Clock initialization is currently done at the examples level. | ||
| 20 | |||
| 21 | // --- START LIB --- | ||
| 22 | |||
| 23 | // Use our own instance of Peripherals, until we align `lib.rs` with the EMBASSY-IMXRT approach | ||
| 24 | // Inlined peripherals_definition! to bypass the `Peripherals::take_with_cs()` check | ||
| 25 | // SHOULD NOT BE USED IN THE FINAL VERSION | ||
| 26 | pub mod lib { | ||
| 27 | // embassy_hal_internal::peripherals!(LPUART2, PIO2_2, PIO2_3) | ||
| 28 | |||
| 29 | embassy_hal_internal::peripherals_definition!(LPUART2, PIO2_2, PIO2_3,); | ||
| 30 | #[doc = r" Struct containing all the peripheral singletons."] | ||
| 31 | #[doc = r""] | ||
| 32 | #[doc = r" To obtain the peripherals, you must initialize the HAL, by calling [`crate::init`]."] | ||
| 33 | #[allow(non_snake_case)] | ||
| 34 | pub struct Peripherals { | ||
| 35 | #[doc = concat!(stringify!(LPUART2)," peripheral")] | ||
| 36 | pub LPUART2: embassy_hal_internal::Peri<'static, peripherals::LPUART2>, | ||
| 37 | #[doc = concat!(stringify!(PIO2_2)," peripheral")] | ||
| 38 | pub PIO2_2: embassy_hal_internal::Peri<'static, peripherals::PIO2_2>, | ||
| 39 | #[doc = concat!(stringify!(PIO2_3)," peripheral")] | ||
| 40 | pub PIO2_3: embassy_hal_internal::Peri<'static, peripherals::PIO2_3>, | ||
| 41 | } | ||
| 42 | impl Peripherals { | ||
| 43 | #[doc = r"Returns all the peripherals *once*"] | ||
| 44 | #[inline] | ||
| 45 | pub(crate) fn take() -> Self { | ||
| 46 | critical_section::with(Self::take_with_cs) | ||
| 47 | } | ||
| 48 | #[doc = r"Returns all the peripherals *once*"] | ||
| 49 | #[inline] | ||
| 50 | pub(crate) fn take_with_cs(_cs: critical_section::CriticalSection) -> Self { | ||
| 51 | #[no_mangle] | ||
| 52 | static mut _EMBASSY_DEVICE_PERIPHERALS2: bool = false; // ALIGN: Temporary fix to use stub Peripherals | ||
| 53 | unsafe { | ||
| 54 | if _EMBASSY_DEVICE_PERIPHERALS2 { | ||
| 55 | panic!("init called more than once!") | ||
| 56 | } | ||
| 57 | _EMBASSY_DEVICE_PERIPHERALS2 = true; | ||
| 58 | Self::steal() | ||
| 59 | } | ||
| 60 | } | ||
| 61 | } | ||
| 62 | impl Peripherals { | ||
| 63 | #[doc = r" Unsafely create an instance of this peripheral out of thin air."] | ||
| 64 | #[doc = r""] | ||
| 65 | #[doc = r" # Safety"] | ||
| 66 | #[doc = r""] | ||
| 67 | #[doc = r" You must ensure that you're only using one instance of this type at a time."] | ||
| 68 | #[inline] | ||
| 69 | pub unsafe fn steal() -> Self { | ||
| 70 | Self { | ||
| 71 | LPUART2: peripherals::LPUART2::steal(), | ||
| 72 | PIO2_2: peripherals::PIO2_2::steal(), | ||
| 73 | PIO2_3: peripherals::PIO2_3::steal(), | ||
| 74 | } | ||
| 75 | } | ||
| 76 | } | ||
| 77 | |||
| 78 | /// Initialize HAL | ||
| 79 | pub fn init() -> Peripherals { | ||
| 80 | Peripherals::take() | ||
| 81 | } | ||
| 82 | } | ||
| 83 | |||
| 84 | // --- END LIB --- | ||
| 85 | |||
| 86 | // --- START GPIO --- | ||
| 87 | |||
| 88 | mod gpio { | ||
| 89 | use embassy_hal_internal::PeripheralType; | ||
| 90 | trait SealedPin {} | ||
| 91 | |||
| 92 | #[allow(private_bounds)] | ||
| 93 | pub trait GpioPin: SealedPin + Sized + PeripheralType + Into<AnyPin> + 'static { | ||
| 94 | /// Type-erase the pin. | ||
| 95 | fn degrade(self) -> AnyPin { | ||
| 96 | todo!() | ||
| 97 | } | ||
| 98 | } | ||
| 99 | |||
| 100 | // Add this macro to implement GpioPin for all pins | ||
| 101 | macro_rules! impl_gpio_pin { | ||
| 102 | ($($pin:ident),*) => { | ||
| 103 | $( | ||
| 104 | impl SealedPin for super::lib::peripherals::$pin {} | ||
| 105 | |||
| 106 | impl GpioPin for super::lib::peripherals::$pin {} | ||
| 107 | |||
| 108 | impl Into<AnyPin> for super::lib::peripherals::$pin { | ||
| 109 | fn into(self) -> AnyPin { | ||
| 110 | AnyPin | ||
| 111 | } | ||
| 112 | } | ||
| 113 | )* | ||
| 114 | }; | ||
| 115 | } | ||
| 116 | |||
| 117 | // Implement GpioPin for all pins from lib.rs | ||
| 118 | impl_gpio_pin!(PIO2_2, PIO2_3); | ||
| 119 | |||
| 120 | #[derive(Debug, Clone, Copy)] | ||
| 121 | pub struct AnyPin; | ||
| 122 | |||
| 123 | impl PeripheralType for AnyPin {} | ||
| 124 | |||
| 125 | pub enum Alt { | ||
| 126 | ALT3, | ||
| 127 | } | ||
| 128 | } | ||
| 129 | |||
| 130 | use gpio::{AnyPin, GpioPin as Pin}; | ||
| 131 | |||
| 132 | // --- END GPIO --- | ||
| 133 | |||
| 134 | // --- START DMA --- | ||
| 135 | mod dma { | ||
| 136 | pub struct Channel<'d> { | ||
| 137 | pub(super) _lifetime: core::marker::PhantomData<&'d ()>, | ||
| 138 | } | ||
| 139 | } | ||
| 140 | |||
| 141 | use dma::Channel; | ||
| 142 | |||
| 143 | // --- END DMA --- | ||
| 144 | |||
| 145 | // --- START CLOCK --- | ||
| 146 | mod clock { | ||
| 147 | #[derive(Debug, Clone, Copy)] | ||
| 148 | pub enum Clock { | ||
| 149 | FroLf, // Low-Frequency Free-Running Oscillator | ||
| 150 | } | ||
| 151 | } | ||
| 152 | |||
| 153 | use clock::Clock; | ||
| 154 | |||
| 155 | // --- END CLOCK --- | ||
| 156 | |||
| 157 | // ============================================================================ | ||
| 158 | // MISC | ||
| 159 | // ============================================================================ | ||
| 160 | |||
| 161 | mod sealed { | ||
| 162 | /// Simply seal a trait to prevent external implementations | ||
| 163 | pub trait Sealed {} | ||
| 164 | } | ||
| 165 | |||
| 166 | // ============================================================================ | ||
| 167 | // INSTANCE TRAIT | ||
| 168 | // ============================================================================ | ||
| 169 | |||
| 170 | pub type Regs = &'static crate::pac::lpuart0::RegisterBlock; | ||
| 171 | |||
| 172 | pub trait SealedInstance { | ||
| 173 | fn info() -> Info; | ||
| 174 | fn index() -> usize; | ||
| 175 | fn buffered_state() -> &'static buffered::State; | ||
| 176 | } | ||
| 177 | |||
| 178 | pub struct Info { | ||
| 179 | pub regs: Regs, | ||
| 180 | } | ||
| 181 | |||
| 182 | /// Trait for LPUART peripheral instances | ||
| 183 | #[allow(private_bounds)] | ||
| 184 | pub trait Instance: SealedInstance + PeripheralType + 'static + Send { | ||
| 185 | type Interrupt: interrupt::typelevel::Interrupt; | ||
| 186 | } | ||
| 187 | |||
| 188 | macro_rules! impl_instance { | ||
| 189 | ($($n:expr),*) => { | ||
| 190 | $( | ||
| 191 | paste!{ | ||
| 192 | impl SealedInstance for lib::peripherals::[<LPUART $n>] { | ||
| 193 | fn info() -> Info { | ||
| 194 | Info { | ||
| 195 | regs: unsafe { &*pac::[<Lpuart $n>]::ptr() }, | ||
| 196 | } | ||
| 197 | } | ||
| 198 | |||
| 199 | #[inline] | ||
| 200 | fn index() -> usize { | ||
| 201 | $n | ||
| 202 | } | ||
| 203 | |||
| 204 | fn buffered_state() -> &'static buffered::State { | ||
| 205 | static BUFFERED_STATE: buffered::State = buffered::State::new(); | ||
| 206 | &BUFFERED_STATE | ||
| 207 | } | ||
| 208 | } | ||
| 209 | |||
| 210 | impl Instance for lib::peripherals::[<LPUART $n>] { | ||
| 211 | type Interrupt = crate::interrupt::typelevel::[<LPUART $n>]; | ||
| 212 | } | ||
| 213 | } | ||
| 214 | )* | ||
| 215 | }; | ||
| 216 | } | ||
| 217 | |||
| 218 | // impl_instance!(0, 1, 2, 3, 4); | ||
| 219 | impl_instance!(2); | ||
| 220 | |||
| 221 | // ============================================================================ | ||
| 222 | // INSTANCE HELPER FUNCTIONS | ||
| 223 | // ============================================================================ | ||
| 224 | |||
| 225 | /// Perform software reset on the LPUART peripheral | ||
| 226 | pub fn perform_software_reset(regs: Regs) { | ||
| 227 | // Software reset - set and clear RST bit (Global register) | ||
| 228 | regs.global().write(|w| w.rst().reset()); | ||
| 229 | regs.global().write(|w| w.rst().no_effect()); | ||
| 230 | } | ||
| 231 | |||
| 232 | /// Disable both transmitter and receiver | ||
| 233 | pub fn disable_transceiver(regs: Regs) { | ||
| 234 | regs.ctrl().modify(|_, w| w.te().disabled().re().disabled()); | ||
| 235 | } | ||
| 236 | |||
| 237 | /// Calculate and configure baudrate settings | ||
| 238 | pub fn configure_baudrate(regs: Regs, baudrate_bps: u32, clock: Clock) -> Result<()> { | ||
| 239 | let clock_freq = get_fc_freq(clock)?; | ||
| 240 | let (osr, sbr) = calculate_baudrate(baudrate_bps, clock_freq)?; | ||
| 241 | |||
| 242 | // Configure BAUD register | ||
| 243 | regs.baud().modify(|_, w| unsafe { | ||
| 244 | // Clear and set OSR | ||
| 245 | w.osr().bits((osr - 1) as u8); | ||
| 246 | // Clear and set SBR | ||
| 247 | w.sbr().bits(sbr); | ||
| 248 | // Set BOTHEDGE if OSR is between 4 and 7 | ||
| 249 | if osr > 3 && osr < 8 { | ||
| 250 | w.bothedge().enabled() | ||
| 251 | } else { | ||
| 252 | w.bothedge().disabled() | ||
| 253 | } | ||
| 254 | }); | ||
| 255 | |||
| 256 | Ok(()) | ||
| 257 | } | ||
| 258 | |||
| 259 | /// Configure frame format (stop bits, data bits) | ||
| 260 | pub fn configure_frame_format(regs: Regs, config: &Config) { | ||
| 261 | // Configure stop bits | ||
| 262 | regs.baud().modify(|_, w| w.sbns().variant(config.stop_bits_count)); | ||
| 263 | |||
| 264 | // Clear M10 for now (10-bit mode) | ||
| 265 | regs.baud().modify(|_, w| w.m10().disabled()); | ||
| 266 | } | ||
| 267 | |||
| 268 | /// Configure control settings (parity, data bits, idle config, pin swap) | ||
| 269 | pub fn configure_control_settings(regs: Regs, config: &Config) { | ||
| 270 | regs.ctrl().modify(|_, w| { | ||
| 271 | // Parity configuration | ||
| 272 | let mut w = if let Some(parity) = config.parity_mode { | ||
| 273 | w.pe().enabled().pt().variant(parity) | ||
| 274 | } else { | ||
| 275 | w.pe().disabled() | ||
| 276 | }; | ||
| 277 | |||
| 278 | // Data bits configuration | ||
| 279 | w = match config.data_bits_count { | ||
| 280 | DataBits::Data8 => { | ||
| 281 | if config.parity_mode.is_some() { | ||
| 282 | w.m().data9() // 8 data + 1 parity = 9 bits | ||
| 283 | } else { | ||
| 284 | w.m().data8() // 8 data bits only | ||
| 285 | } | ||
| 286 | } | ||
| 287 | DataBits::Data9 => w.m().data9(), | ||
| 288 | }; | ||
| 289 | |||
| 290 | // Idle configuration | ||
| 291 | w = w.idlecfg().variant(config.rx_idle_config); | ||
| 292 | w = w.ilt().variant(config.rx_idle_type); | ||
| 293 | |||
| 294 | // Swap TXD/RXD if configured | ||
| 295 | if config.swap_txd_rxd { | ||
| 296 | w.swap().swap() | ||
| 297 | } else { | ||
| 298 | w.swap().standard() | ||
| 299 | } | ||
| 300 | }); | ||
| 301 | } | ||
| 302 | |||
| 303 | /// Configure FIFO settings and watermarks | ||
| 304 | pub fn configure_fifo(regs: Regs, config: &Config) { | ||
| 305 | // Configure WATER register for FIFO watermarks | ||
| 306 | regs.water().write(|w| unsafe { | ||
| 307 | w.rxwater() | ||
| 308 | .bits(config.rx_fifo_watermark as u8) | ||
| 309 | .txwater() | ||
| 310 | .bits(config.tx_fifo_watermark as u8) | ||
| 311 | }); | ||
| 312 | |||
| 313 | // Enable TX/RX FIFOs | ||
| 314 | regs.fifo().modify(|_, w| w.txfe().enabled().rxfe().enabled()); | ||
| 315 | |||
| 316 | // Flush FIFOs | ||
| 317 | regs.fifo() | ||
| 318 | .modify(|_, w| w.txflush().txfifo_rst().rxflush().rxfifo_rst()); | ||
| 319 | } | ||
| 320 | |||
| 321 | /// Clear all status flags | ||
| 322 | pub fn clear_all_status_flags(regs: Regs) { | ||
| 323 | regs.stat().reset(); | ||
| 324 | } | ||
| 325 | |||
| 326 | /// Configure hardware flow control if enabled | ||
| 327 | pub fn configure_flow_control(regs: Regs, config: &Config) { | ||
| 328 | if config.enable_rx_rts || config.enable_tx_cts { | ||
| 329 | regs.modir().modify(|_, w| { | ||
| 330 | let mut w = w; | ||
| 331 | |||
| 332 | // Configure TX CTS | ||
| 333 | w = w.txctsc().variant(config.tx_cts_config); | ||
| 334 | w = w.txctssrc().variant(config.tx_cts_source); | ||
| 335 | |||
| 336 | if config.enable_rx_rts { | ||
| 337 | w = w.rxrtse().enabled(); | ||
| 338 | } else { | ||
| 339 | w = w.rxrtse().disabled(); | ||
| 340 | } | ||
| 341 | |||
| 342 | if config.enable_tx_cts { | ||
| 343 | w = w.txctse().enabled(); | ||
| 344 | } else { | ||
| 345 | w = w.txctse().disabled(); | ||
| 346 | } | ||
| 347 | |||
| 348 | w | ||
| 349 | }); | ||
| 350 | } | ||
| 351 | } | ||
| 352 | |||
| 353 | /// Configure bit order (MSB first or LSB first) | ||
| 354 | pub fn configure_bit_order(regs: Regs, msb_first: MsbFirst) { | ||
| 355 | regs.stat().modify(|_, w| w.msbf().variant(msb_first)); | ||
| 356 | } | ||
| 357 | |||
| 358 | /// Enable transmitter and/or receiver based on configuration | ||
| 359 | pub fn enable_transceiver(regs: Regs, enable_tx: bool, enable_rx: bool) { | ||
| 360 | regs.ctrl().modify(|_, w| { | ||
| 361 | let mut w = w; | ||
| 362 | if enable_tx { | ||
| 363 | w = w.te().enabled(); | ||
| 364 | } | ||
| 365 | if enable_rx { | ||
| 366 | w = w.re().enabled(); | ||
| 367 | } | ||
| 368 | w | ||
| 369 | }); | ||
| 370 | } | ||
| 371 | |||
| 372 | pub fn calculate_baudrate(baudrate: u32, src_clock_hz: u32) -> Result<(u8, u16)> { | ||
| 373 | let mut baud_diff = baudrate; | ||
| 374 | let mut osr = 0u8; | ||
| 375 | let mut sbr = 0u16; | ||
| 376 | |||
| 377 | // Try OSR values from 4 to 32 | ||
| 378 | for osr_temp in 4u8..=32u8 { | ||
| 379 | // Calculate SBR: (srcClock_Hz * 2 / (baudRate * osr) + 1) / 2 | ||
| 380 | let sbr_calc = ((src_clock_hz * 2) / (baudrate * osr_temp as u32) + 1) / 2; | ||
| 381 | |||
| 382 | let sbr_temp = if sbr_calc == 0 { | ||
| 383 | 1 | ||
| 384 | } else if sbr_calc > 0x1FFF { | ||
| 385 | 0x1FFF | ||
| 386 | } else { | ||
| 387 | sbr_calc as u16 | ||
| 388 | }; | ||
| 389 | |||
| 390 | // Calculate actual baud rate | ||
| 391 | let calculated_baud = src_clock_hz / (osr_temp as u32 * sbr_temp as u32); | ||
| 392 | |||
| 393 | let temp_diff = if calculated_baud > baudrate { | ||
| 394 | calculated_baud - baudrate | ||
| 395 | } else { | ||
| 396 | baudrate - calculated_baud | ||
| 397 | }; | ||
| 398 | |||
| 399 | if temp_diff <= baud_diff { | ||
| 400 | baud_diff = temp_diff; | ||
| 401 | osr = osr_temp; | ||
| 402 | sbr = sbr_temp; | ||
| 403 | } | ||
| 404 | } | ||
| 405 | |||
| 406 | // Check if baud rate difference is within 3% | ||
| 407 | if baud_diff > (baudrate / 100) * 3 { | ||
| 408 | return Err(Error::UnsupportedBaudrate); | ||
| 409 | } | ||
| 410 | |||
| 411 | Ok((osr, sbr)) | ||
| 412 | } | ||
| 413 | |||
| 414 | pub fn get_fc_freq(clock: Clock) -> Result<u32> { | ||
| 415 | // This is a placeholder - actual implementation would query the clock system | ||
| 416 | // In real implementation, this would get the LPUART clock frequency | ||
| 417 | match clock { | ||
| 418 | Clock::FroLf => Ok(12_000_000), // Low frequency oscillator | ||
| 419 | #[allow(unreachable_patterns)] | ||
| 420 | _ => Err(Error::InvalidArgument), | ||
| 421 | } | ||
| 422 | } | ||
| 423 | |||
| 424 | /// Wait for all transmit operations to complete | ||
| 425 | pub fn wait_for_tx_complete(regs: Regs) { | ||
| 426 | // Wait for TX FIFO to empty | ||
| 427 | while regs.water().read().txcount().bits() != 0 { | ||
| 428 | // Wait for TX FIFO to drain | ||
| 429 | } | ||
| 430 | |||
| 431 | // Wait for last character to shift out (TC = Transmission Complete) | ||
| 432 | while regs.stat().read().tc().is_active() { | ||
| 433 | // Wait for transmission to complete | ||
| 434 | } | ||
| 435 | } | ||
| 436 | |||
| 437 | pub fn check_and_clear_rx_errors(regs: Regs) -> Result<()> { | ||
| 438 | let stat = regs.stat().read(); | ||
| 439 | let mut status = Ok(()); | ||
| 440 | |||
| 441 | // Check for overrun first - other error flags are prevented when OR is set | ||
| 442 | if stat.or().is_overrun() { | ||
| 443 | regs.stat().write(|w| w.or().clear_bit_by_one()); | ||
| 444 | |||
| 445 | return Err(Error::Overrun); | ||
| 446 | } | ||
| 447 | |||
| 448 | if stat.pf().is_parity() { | ||
| 449 | regs.stat().write(|w| w.pf().clear_bit_by_one()); | ||
| 450 | status = Err(Error::Parity); | ||
| 451 | } | ||
| 452 | |||
| 453 | if stat.fe().is_error() { | ||
| 454 | regs.stat().write(|w| w.fe().clear_bit_by_one()); | ||
| 455 | status = Err(Error::Framing); | ||
| 456 | } | ||
| 457 | |||
| 458 | if stat.nf().is_noise() { | ||
| 459 | regs.stat().write(|w| w.nf().clear_bit_by_one()); | ||
| 460 | status = Err(Error::Noise); | ||
| 461 | } | ||
| 462 | |||
| 463 | status | ||
| 464 | } | ||
| 465 | |||
| 466 | pub fn has_data(regs: Regs) -> bool { | ||
| 467 | if regs.param().read().rxfifo().bits() > 0 { | ||
| 468 | // FIFO is available - check RXCOUNT in WATER register | ||
| 469 | regs.water().read().rxcount().bits() > 0 | ||
| 470 | } else { | ||
| 471 | // No FIFO - check RDRF flag in STAT register | ||
| 472 | regs.stat().read().rdrf().is_rxdata() | ||
| 473 | } | ||
| 474 | } | ||
| 475 | |||
| 476 | // ============================================================================ | ||
| 477 | // PIN TRAITS FOR LPUART FUNCTIONALITY | ||
| 478 | // ============================================================================ | ||
| 479 | |||
| 480 | impl<T: Pin> sealed::Sealed for T {} | ||
| 481 | |||
| 482 | /// io configuration trait for Lpuart Tx configuration | ||
| 483 | pub trait TxPin<T: Instance>: Pin + sealed::Sealed + PeripheralType { | ||
| 484 | /// convert the pin to appropriate function for Lpuart Tx usage | ||
| 485 | fn as_tx(&self); | ||
| 486 | } | ||
| 487 | |||
| 488 | /// io configuration trait for Lpuart Rx configuration | ||
| 489 | pub trait RxPin<T: Instance>: Pin + sealed::Sealed + PeripheralType { | ||
| 490 | /// convert the pin to appropriate function for Lpuart Rx usage | ||
| 491 | fn as_rx(&self); | ||
| 492 | } | ||
| 493 | |||
| 494 | /// io configuration trait for Lpuart Cts | ||
| 495 | pub trait CtsPin<T: Instance>: Pin + sealed::Sealed + PeripheralType { | ||
| 496 | /// convert the pin to appropriate function for Lpuart Cts usage | ||
| 497 | fn as_cts(&self); | ||
| 498 | } | ||
| 499 | |||
| 500 | /// io configuration trait for Lpuart Rts | ||
| 501 | pub trait RtsPin<T: Instance>: Pin + sealed::Sealed + PeripheralType { | ||
| 502 | /// convert the pin to appropriate function for Lpuart Rts usage | ||
| 503 | fn as_rts(&self); | ||
| 504 | } | ||
| 505 | |||
| 506 | macro_rules! impl_pin_trait { | ||
| 507 | ($fcn:ident, $mode:ident, $($pin:ident, $alt:ident),*) => { | ||
| 508 | paste! { | ||
| 509 | $( | ||
| 510 | impl [<$mode:camel Pin>]<lib::peripherals::$fcn> for lib::peripherals::$pin { | ||
| 511 | fn [<as_ $mode>](&self) { | ||
| 512 | let _alt = gpio::Alt::$alt; | ||
| 513 | // todo!("Configure pin for LPUART function") | ||
| 514 | } | ||
| 515 | } | ||
| 516 | )* | ||
| 517 | } | ||
| 518 | }; | ||
| 519 | } | ||
| 520 | |||
| 521 | // Document identifier: MCXA343/344 Rev. 1DraftB ReleaseCandidate, 2025-07-10 - 6.1 MCX A173, A174 Signal Multiplexing and Pin Assignments | ||
| 522 | // impl_pin_trait!(LPUART0, rx, PIO2_0, ALT2, PIO0_2, ALT2, PIO0_20, ALT3); | ||
| 523 | // impl_pin_trait!(LPUART0, tx, PIO2_1, ALT2, PIO0_3, ALT2, PIO0_21, ALT3); | ||
| 524 | // impl_pin_trait!(LPUART0, rts, PIO2_2, ALT2, PIO0_0, ALT2, PIO0_22, ALT3); | ||
| 525 | // impl_pin_trait!(LPUART0, cts, PIO2_3, ALT2, PIO0_1, ALT2, PIO0_23, ALT3); | ||
| 526 | impl_pin_trait!(LPUART2, rx, PIO2_3, ALT3); | ||
| 527 | impl_pin_trait!(LPUART2, tx, PIO2_2, ALT3); | ||
| 528 | |||
| 529 | // ============================================================================ | ||
| 530 | // ERROR TYPES AND RESULTS | ||
| 531 | // ============================================================================ | ||
| 532 | |||
| 533 | /// LPUART error types | ||
| 534 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] | ||
| 535 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 536 | pub enum Error { | ||
| 537 | /// Read error | ||
| 538 | Read, | ||
| 539 | /// Buffer overflow | ||
| 540 | Overrun, | ||
| 541 | /// Noise error | ||
| 542 | Noise, | ||
| 543 | /// Framing error | ||
| 544 | Framing, | ||
| 545 | /// Parity error | ||
| 546 | Parity, | ||
| 547 | /// Failure | ||
| 548 | Fail, | ||
| 549 | /// Invalid argument | ||
| 550 | InvalidArgument, | ||
| 551 | /// Lpuart baud rate cannot be supported with the given clock | ||
| 552 | UnsupportedBaudrate, | ||
| 553 | /// RX FIFO Empty | ||
| 554 | RxFifoEmpty, | ||
| 555 | /// TX FIFO Full | ||
| 556 | TxFifoFull, | ||
| 557 | /// TX Busy | ||
| 558 | TxBusy, | ||
| 559 | } | ||
| 560 | |||
| 561 | /// A specialized Result type for LPUART operations | ||
| 562 | pub type Result<T> = core::result::Result<T, Error>; | ||
| 563 | |||
| 564 | // ============================================================================ | ||
| 565 | // CONFIGURATION STRUCTURES | ||
| 566 | // ============================================================================ | ||
| 567 | |||
| 568 | /// Lpuart config | ||
| 569 | #[derive(Debug, Clone, Copy)] | ||
| 570 | pub struct Config { | ||
| 571 | /// Baud rate in bits per second | ||
| 572 | pub baudrate_bps: u32, | ||
| 573 | /// Clock | ||
| 574 | pub clock: Clock, | ||
| 575 | /// Parity configuration | ||
| 576 | pub parity_mode: Option<Parity>, | ||
| 577 | /// Number of data bits | ||
| 578 | pub data_bits_count: DataBits, | ||
| 579 | /// MSB First or LSB First configuration | ||
| 580 | pub msb_firs: MsbFirst, | ||
| 581 | /// Number of stop bits | ||
| 582 | pub stop_bits_count: StopBits, | ||
| 583 | /// TX FIFO watermark | ||
| 584 | pub tx_fifo_watermark: u8, | ||
| 585 | /// RX FIFO watermark | ||
| 586 | pub rx_fifo_watermark: u8, | ||
| 587 | /// RX RTS enable | ||
| 588 | pub enable_rx_rts: bool, | ||
| 589 | /// TX CTS enable | ||
| 590 | pub enable_tx_cts: bool, | ||
| 591 | /// TX CTS source | ||
| 592 | pub tx_cts_source: TxCtsSource, | ||
| 593 | /// TX CTS configure | ||
| 594 | pub tx_cts_config: TxCtsConfig, | ||
| 595 | /// RX IDLE type | ||
| 596 | pub rx_idle_type: IdleType, | ||
| 597 | /// RX IDLE configuration | ||
| 598 | pub rx_idle_config: IdleConfig, | ||
| 599 | /// Enable transmitter | ||
| 600 | pub enable_tx: bool, | ||
| 601 | /// Enable receiver | ||
| 602 | pub enable_rx: bool, | ||
| 603 | /// Swap TXD and RXD pins | ||
| 604 | pub swap_txd_rxd: bool, | ||
| 605 | } | ||
| 606 | |||
| 607 | impl Default for Config { | ||
| 608 | fn default() -> Self { | ||
| 609 | Self { | ||
| 610 | baudrate_bps: 115_200u32, | ||
| 611 | clock: Clock::FroLf, | ||
| 612 | parity_mode: None, | ||
| 613 | data_bits_count: DataBits::Data8, | ||
| 614 | msb_firs: MsbFirst::LsbFirst, | ||
| 615 | stop_bits_count: StopBits::One, | ||
| 616 | tx_fifo_watermark: 0, | ||
| 617 | rx_fifo_watermark: 1, | ||
| 618 | enable_rx_rts: false, | ||
| 619 | enable_tx_cts: false, | ||
| 620 | tx_cts_source: TxCtsSource::Cts, | ||
| 621 | tx_cts_config: TxCtsConfig::Start, | ||
| 622 | rx_idle_type: IdleType::FromStart, | ||
| 623 | rx_idle_config: IdleConfig::Idle1, | ||
| 624 | enable_tx: false, | ||
| 625 | enable_rx: false, | ||
| 626 | swap_txd_rxd: false, | ||
| 627 | } | ||
| 628 | } | ||
| 629 | } | ||
| 630 | |||
| 631 | /// LPUART status flags | ||
| 632 | #[derive(Debug, Clone, Copy)] | ||
| 633 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 634 | pub struct Status { | ||
| 635 | /// Transmit data register empty | ||
| 636 | pub tx_empty: bool, | ||
| 637 | /// Transmission complete | ||
| 638 | pub tx_complete: bool, | ||
| 639 | /// Receive data register full | ||
| 640 | pub rx_full: bool, | ||
| 641 | /// Idle line detected | ||
| 642 | pub idle: bool, | ||
| 643 | /// Receiver overrun | ||
| 644 | pub overrun: bool, | ||
| 645 | /// Noise error | ||
| 646 | pub noise: bool, | ||
| 647 | /// Framing error | ||
| 648 | pub framing: bool, | ||
| 649 | /// Parity error | ||
| 650 | pub parity: bool, | ||
| 651 | } | ||
| 652 | |||
| 653 | // ============================================================================ | ||
| 654 | // MODE TRAITS (BLOCKING/ASYNC) | ||
| 655 | // ============================================================================ | ||
| 656 | |||
| 657 | /// Driver move trait. | ||
| 658 | #[allow(private_bounds)] | ||
| 659 | pub trait Mode: sealed::Sealed {} | ||
| 660 | |||
| 661 | /// Blocking mode. | ||
| 662 | pub struct Blocking; | ||
| 663 | impl sealed::Sealed for Blocking {} | ||
| 664 | impl Mode for Blocking {} | ||
| 665 | |||
| 666 | /// Async mode. | ||
| 667 | pub struct Async; | ||
| 668 | impl sealed::Sealed for Async {} | ||
| 669 | impl Mode for Async {} | ||
| 670 | |||
| 671 | // ============================================================================ | ||
| 672 | // CORE DRIVER STRUCTURES | ||
| 673 | // ============================================================================ | ||
| 674 | |||
| 675 | /// Lpuart driver. | ||
| 676 | pub struct Lpuart<'a, M: Mode> { | ||
| 677 | info: Info, | ||
| 678 | tx: LpuartTx<'a, M>, | ||
| 679 | rx: LpuartRx<'a, M>, | ||
| 680 | } | ||
| 681 | |||
| 682 | /// Lpuart TX driver. | ||
| 683 | pub struct LpuartTx<'a, M: Mode> { | ||
| 684 | info: Info, | ||
| 685 | _tx_pin: Peri<'a, AnyPin>, | ||
| 686 | _tx_dma: Option<Channel<'a>>, | ||
| 687 | mode: PhantomData<(&'a (), M)>, | ||
| 688 | } | ||
| 689 | |||
| 690 | /// Lpuart Rx driver. | ||
| 691 | pub struct LpuartRx<'a, M: Mode> { | ||
| 692 | info: Info, | ||
| 693 | _rx_pin: Peri<'a, AnyPin>, | ||
| 694 | _rx_dma: Option<Channel<'a>>, | ||
| 695 | mode: PhantomData<(&'a (), M)>, | ||
| 696 | } | ||
| 697 | |||
| 698 | // ============================================================================ | ||
| 699 | // LPUART CORE IMPLEMENTATION | ||
| 700 | // ============================================================================ | ||
| 701 | |||
| 702 | impl<'a, M: Mode> Lpuart<'a, M> { | ||
| 703 | fn init<T: Instance>( | ||
| 704 | _tx: Option<&Peri<'a, AnyPin>>, | ||
| 705 | _rx: Option<&Peri<'a, AnyPin>>, | ||
| 706 | _rts: Option<&Peri<'a, AnyPin>>, | ||
| 707 | _cts: Option<&Peri<'a, AnyPin>>, | ||
| 708 | config: Config, | ||
| 709 | ) -> Result<()> { | ||
| 710 | let regs = T::info().regs; | ||
| 711 | |||
| 712 | // Perform initialization sequence | ||
| 713 | perform_software_reset(regs); | ||
| 714 | disable_transceiver(regs); | ||
| 715 | configure_baudrate(regs, config.baudrate_bps, config.clock)?; | ||
| 716 | configure_frame_format(regs, &config); | ||
| 717 | configure_control_settings(regs, &config); | ||
| 718 | configure_fifo(regs, &config); | ||
| 719 | clear_all_status_flags(regs); | ||
| 720 | configure_flow_control(regs, &config); | ||
| 721 | configure_bit_order(regs, config.msb_firs); | ||
| 722 | enable_transceiver(regs, config.enable_tx, config.enable_rx); | ||
| 723 | |||
| 724 | Ok(()) | ||
| 725 | } | ||
| 726 | |||
| 727 | /// Deinitialize the LPUART peripheral | ||
| 728 | pub fn deinit(&self) -> Result<()> { | ||
| 729 | let regs = self.info.regs; | ||
| 730 | |||
| 731 | // Wait for TX operations to complete | ||
| 732 | wait_for_tx_complete(regs); | ||
| 733 | |||
| 734 | // Clear all status flags | ||
| 735 | clear_all_status_flags(regs); | ||
| 736 | |||
| 737 | // Disable the module - clear all CTRL register bits | ||
| 738 | regs.ctrl().reset(); | ||
| 739 | |||
| 740 | Ok(()) | ||
| 741 | } | ||
| 742 | |||
| 743 | /// Split the Lpuart into a transmitter and receiver | ||
| 744 | pub fn split(self) -> (LpuartTx<'a, M>, LpuartRx<'a, M>) { | ||
| 745 | (self.tx, self.rx) | ||
| 746 | } | ||
| 747 | |||
| 748 | /// Split the Lpuart into a transmitter and receiver by mutable reference | ||
| 749 | pub fn split_ref(&mut self) -> (&mut LpuartTx<'a, M>, &mut LpuartRx<'a, M>) { | ||
| 750 | (&mut self.tx, &mut self.rx) | ||
| 751 | } | ||
| 752 | } | ||
| 753 | |||
| 754 | // ============================================================================ | ||
| 755 | // BLOCKING MODE IMPLEMENTATIONS | ||
| 756 | // ============================================================================ | ||
| 757 | |||
| 758 | impl<'a> Lpuart<'a, Blocking> { | ||
| 759 | /// Create a new blocking LPUART instance with TX and RX pins. | ||
| 760 | pub fn new_blocking<T: Instance>( | ||
| 761 | _inner: Peri<'a, T>, | ||
| 762 | tx_pin: Peri<'a, impl TxPin<T>>, | ||
| 763 | rx_pin: Peri<'a, impl RxPin<T>>, | ||
| 764 | config: Config, | ||
| 765 | ) -> Result<Self> { | ||
| 766 | // Configure the pins for LPUART usage | ||
| 767 | tx_pin.as_tx(); | ||
| 768 | rx_pin.as_rx(); | ||
| 769 | |||
| 770 | // Convert pins to AnyPin | ||
| 771 | let tx_pin: Peri<'a, AnyPin> = tx_pin.into(); | ||
| 772 | let rx_pin: Peri<'a, AnyPin> = rx_pin.into(); | ||
| 773 | |||
| 774 | // Initialize the peripheral | ||
| 775 | Self::init::<T>(Some(&tx_pin), Some(&rx_pin), None, None, config)?; | ||
| 776 | |||
| 777 | Ok(Self { | ||
| 778 | info: T::info(), | ||
| 779 | tx: LpuartTx::new_inner(T::info(), tx_pin, None), | ||
| 780 | rx: LpuartRx::new_inner(T::info(), rx_pin, None), | ||
| 781 | }) | ||
| 782 | } | ||
| 783 | } | ||
| 784 | |||
| 785 | // ---------------------------------------------------------------------------- | ||
| 786 | // Blocking TX Implementation | ||
| 787 | // ---------------------------------------------------------------------------- | ||
| 788 | |||
| 789 | impl<'a, M: Mode> LpuartTx<'a, M> { | ||
| 790 | fn new_inner(info: Info, tx_pin: Peri<'a, AnyPin>, tx_dma: Option<Channel<'a>>) -> Self { | ||
| 791 | Self { | ||
| 792 | info, | ||
| 793 | _tx_pin: tx_pin, | ||
| 794 | _tx_dma: tx_dma, | ||
| 795 | mode: PhantomData, | ||
| 796 | } | ||
| 797 | } | ||
| 798 | } | ||
| 799 | |||
| 800 | impl<'a> LpuartTx<'a, Blocking> { | ||
| 801 | /// Create a new blocking LPUART which can only send data. | ||
| 802 | pub fn new_blocking<T: Instance>( | ||
| 803 | _inner: Peri<'a, T>, | ||
| 804 | tx_pin: Peri<'a, impl TxPin<T>>, | ||
| 805 | config: Config, | ||
| 806 | ) -> Result<Self> { | ||
| 807 | tx_pin.as_tx(); | ||
| 808 | |||
| 809 | let tx_pin: Peri<'a, AnyPin> = tx_pin.into(); | ||
| 810 | |||
| 811 | Lpuart::<Blocking>::init::<T>(Some(&tx_pin), None, None, None, config)?; | ||
| 812 | |||
| 813 | Ok(Self::new_inner(T::info(), tx_pin, None)) | ||
| 814 | } | ||
| 815 | |||
| 816 | fn write_byte_internal(&mut self, byte: u8) -> Result<()> { | ||
| 817 | self.info.regs.data().modify(|_, w| unsafe { w.bits(u32::from(byte)) }); | ||
| 818 | |||
| 819 | Ok(()) | ||
| 820 | } | ||
| 821 | |||
| 822 | fn blocking_write_byte(&mut self, byte: u8) -> Result<()> { | ||
| 823 | while self.info.regs.stat().read().tdre().is_txdata() {} | ||
| 824 | self.write_byte_internal(byte) | ||
| 825 | } | ||
| 826 | |||
| 827 | fn write_byte(&mut self, byte: u8) -> Result<()> { | ||
| 828 | if self.info.regs.stat().read().tdre().is_txdata() { | ||
| 829 | Err(Error::TxFifoFull) | ||
| 830 | } else { | ||
| 831 | self.write_byte_internal(byte) | ||
| 832 | } | ||
| 833 | } | ||
| 834 | |||
| 835 | /// Write data to LPUART TX blocking execution until all data is sent. | ||
| 836 | pub fn blocking_write(&mut self, buf: &[u8]) -> Result<()> { | ||
| 837 | for x in buf { | ||
| 838 | self.blocking_write_byte(*x)?; | ||
| 839 | } | ||
| 840 | |||
| 841 | Ok(()) | ||
| 842 | } | ||
| 843 | |||
| 844 | /// Write data to LPUART TX without blocking. | ||
| 845 | pub fn write(&mut self, buf: &[u8]) -> Result<()> { | ||
| 846 | for x in buf { | ||
| 847 | self.write_byte(*x)?; | ||
| 848 | } | ||
| 849 | |||
| 850 | Ok(()) | ||
| 851 | } | ||
| 852 | |||
| 853 | /// Flush LPUART TX blocking execution until all data has been transmitted. | ||
| 854 | pub fn blocking_flush(&mut self) -> Result<()> { | ||
| 855 | while self.info.regs.water().read().txcount().bits() != 0 { | ||
| 856 | // Wait for TX FIFO to drain | ||
| 857 | } | ||
| 858 | |||
| 859 | // Wait for last character to shift out | ||
| 860 | while self.info.regs.stat().read().tc().is_active() { | ||
| 861 | // Wait for transmission to complete | ||
| 862 | } | ||
| 863 | |||
| 864 | Ok(()) | ||
| 865 | } | ||
| 866 | |||
| 867 | /// Flush LPUART TX. | ||
| 868 | pub fn flush(&mut self) -> Result<()> { | ||
| 869 | // Check if TX FIFO is empty | ||
| 870 | if self.info.regs.water().read().txcount().bits() != 0 { | ||
| 871 | return Err(Error::TxBusy); | ||
| 872 | } | ||
| 873 | |||
| 874 | // Check if transmission is complete | ||
| 875 | if self.info.regs.stat().read().tc().is_active() { | ||
| 876 | return Err(Error::TxBusy); | ||
| 877 | } | ||
| 878 | |||
| 879 | Ok(()) | ||
| 880 | } | ||
| 881 | } | ||
| 882 | |||
| 883 | // ---------------------------------------------------------------------------- | ||
| 884 | // Blocking RX Implementation | ||
| 885 | // ---------------------------------------------------------------------------- | ||
| 886 | |||
| 887 | impl<'a, M: Mode> LpuartRx<'a, M> { | ||
| 888 | fn new_inner(info: Info, rx_pin: Peri<'a, AnyPin>, rx_dma: Option<Channel<'a>>) -> Self { | ||
| 889 | Self { | ||
| 890 | info, | ||
| 891 | _rx_pin: rx_pin, | ||
| 892 | _rx_dma: rx_dma, | ||
| 893 | mode: PhantomData, | ||
| 894 | } | ||
| 895 | } | ||
| 896 | } | ||
| 897 | |||
| 898 | impl<'a> LpuartRx<'a, Blocking> { | ||
| 899 | /// Create a new blocking LPUART which can only receive data. | ||
| 900 | pub fn new_blocking<T: Instance>( | ||
| 901 | _inner: Peri<'a, T>, | ||
| 902 | rx_pin: Peri<'a, impl RxPin<T>>, | ||
| 903 | config: Config, | ||
| 904 | ) -> Result<Self> { | ||
| 905 | rx_pin.as_rx(); | ||
| 906 | |||
| 907 | let rx_pin: Peri<'a, AnyPin> = rx_pin.into(); | ||
| 908 | |||
| 909 | Lpuart::<Blocking>::init::<T>(None, Some(&rx_pin), None, None, config)?; | ||
| 910 | |||
| 911 | Ok(Self::new_inner(T::info(), rx_pin, None)) | ||
| 912 | } | ||
| 913 | |||
| 914 | fn read_byte_internal(&mut self) -> Result<u8> { | ||
| 915 | let data = self.info.regs.data().read(); | ||
| 916 | |||
| 917 | Ok((data.bits() & 0xFF) as u8) | ||
| 918 | } | ||
| 919 | |||
| 920 | fn read_byte(&mut self) -> Result<u8> { | ||
| 921 | check_and_clear_rx_errors(self.info.regs)?; | ||
| 922 | |||
| 923 | if !has_data(self.info.regs) { | ||
| 924 | return Err(Error::RxFifoEmpty); | ||
| 925 | } | ||
| 926 | |||
| 927 | self.read_byte_internal() | ||
| 928 | } | ||
| 929 | |||
| 930 | fn blocking_read_byte(&mut self) -> Result<u8> { | ||
| 931 | loop { | ||
| 932 | if has_data(self.info.regs) { | ||
| 933 | return self.read_byte_internal(); | ||
| 934 | } | ||
| 935 | |||
| 936 | check_and_clear_rx_errors(self.info.regs)?; | ||
| 937 | } | ||
| 938 | } | ||
| 939 | |||
| 940 | /// Read data from LPUART RX without blocking. | ||
| 941 | pub fn read(&mut self, buf: &mut [u8]) -> Result<()> { | ||
| 942 | for byte in buf.iter_mut() { | ||
| 943 | *byte = self.read_byte()?; | ||
| 944 | } | ||
| 945 | Ok(()) | ||
| 946 | } | ||
| 947 | |||
| 948 | /// Read data from LPUART RX blocking execution until the buffer is filled. | ||
| 949 | pub fn blocking_read(&mut self, buf: &mut [u8]) -> Result<()> { | ||
| 950 | for byte in buf.iter_mut() { | ||
| 951 | *byte = self.blocking_read_byte()?; | ||
| 952 | } | ||
| 953 | Ok(()) | ||
| 954 | } | ||
| 955 | } | ||
| 956 | |||
| 957 | impl<'a> Lpuart<'a, Blocking> { | ||
| 958 | /// Read data from LPUART RX blocking execution until the buffer is filled | ||
| 959 | pub fn blocking_read(&mut self, buf: &mut [u8]) -> Result<()> { | ||
| 960 | self.rx.blocking_read(buf) | ||
| 961 | } | ||
| 962 | |||
| 963 | /// Read data from LPUART RX without blocking | ||
| 964 | pub fn read(&mut self, buf: &mut [u8]) -> Result<()> { | ||
| 965 | self.rx.read(buf) | ||
| 966 | } | ||
| 967 | |||
| 968 | /// Write data to LPUART TX blocking execution until all data is sent | ||
| 969 | pub fn blocking_write(&mut self, buf: &[u8]) -> Result<()> { | ||
| 970 | self.tx.blocking_write(buf) | ||
| 971 | } | ||
| 972 | |||
| 973 | /// Write data to LPUART TX without blocking | ||
| 974 | pub fn write(&mut self, buf: &[u8]) -> Result<()> { | ||
| 975 | self.tx.write(buf) | ||
| 976 | } | ||
| 977 | |||
| 978 | /// Flush LPUART TX blocking execution until all data has been transmitted | ||
| 979 | pub fn blocking_flush(&mut self) -> Result<()> { | ||
| 980 | self.tx.blocking_flush() | ||
| 981 | } | ||
| 982 | |||
| 983 | /// Flush LPUART TX without blocking | ||
| 984 | pub fn flush(&mut self) -> Result<()> { | ||
| 985 | self.tx.flush() | ||
| 986 | } | ||
| 987 | } | ||
| 988 | |||
| 989 | // ============================================================================ | ||
| 990 | // ASYNC MODE IMPLEMENTATIONS | ||
| 991 | // ============================================================================ | ||
| 992 | |||
| 993 | // TODO: Implement async mode for LPUART | ||
| 994 | |||
| 995 | // ============================================================================ | ||
| 996 | // EMBEDDED-HAL 0.2 TRAIT IMPLEMENTATIONS | ||
| 997 | // ============================================================================ | ||
| 998 | |||
| 999 | impl embedded_hal_02::serial::Read<u8> for LpuartRx<'_, Blocking> { | ||
| 1000 | type Error = Error; | ||
| 1001 | |||
| 1002 | fn read(&mut self) -> core::result::Result<u8, nb::Error<Self::Error>> { | ||
| 1003 | let mut buf = [0; 1]; | ||
| 1004 | match self.read(&mut buf) { | ||
| 1005 | Ok(_) => Ok(buf[0]), | ||
| 1006 | Err(Error::RxFifoEmpty) => Err(nb::Error::WouldBlock), | ||
| 1007 | Err(e) => Err(nb::Error::Other(e)), | ||
| 1008 | } | ||
| 1009 | } | ||
| 1010 | } | ||
| 1011 | |||
| 1012 | impl embedded_hal_02::serial::Write<u8> for LpuartTx<'_, Blocking> { | ||
| 1013 | type Error = Error; | ||
| 1014 | |||
| 1015 | fn write(&mut self, word: u8) -> core::result::Result<(), nb::Error<Self::Error>> { | ||
| 1016 | match self.write(&[word]) { | ||
| 1017 | Ok(_) => Ok(()), | ||
| 1018 | Err(Error::TxFifoFull) => Err(nb::Error::WouldBlock), | ||
| 1019 | Err(e) => Err(nb::Error::Other(e)), | ||
| 1020 | } | ||
| 1021 | } | ||
| 1022 | |||
| 1023 | fn flush(&mut self) -> core::result::Result<(), nb::Error<Self::Error>> { | ||
| 1024 | match self.flush() { | ||
| 1025 | Ok(_) => Ok(()), | ||
| 1026 | Err(Error::TxBusy) => Err(nb::Error::WouldBlock), | ||
| 1027 | Err(e) => Err(nb::Error::Other(e)), | ||
| 1028 | } | ||
| 1029 | } | ||
| 1030 | } | ||
| 1031 | |||
| 1032 | impl embedded_hal_02::blocking::serial::Write<u8> for LpuartTx<'_, Blocking> { | ||
| 1033 | type Error = Error; | ||
| 1034 | |||
| 1035 | fn bwrite_all(&mut self, buffer: &[u8]) -> core::result::Result<(), Self::Error> { | ||
| 1036 | self.blocking_write(buffer) | ||
| 1037 | } | ||
| 1038 | |||
| 1039 | fn bflush(&mut self) -> core::result::Result<(), Self::Error> { | ||
| 1040 | self.blocking_flush() | ||
| 1041 | } | ||
| 1042 | } | ||
| 1043 | |||
| 1044 | impl embedded_hal_02::serial::Read<u8> for Lpuart<'_, Blocking> { | ||
| 1045 | type Error = Error; | ||
| 1046 | |||
| 1047 | fn read(&mut self) -> core::result::Result<u8, nb::Error<Self::Error>> { | ||
| 1048 | embedded_hal_02::serial::Read::read(&mut self.rx) | ||
| 1049 | } | ||
| 1050 | } | ||
| 1051 | |||
| 1052 | impl embedded_hal_02::serial::Write<u8> for Lpuart<'_, Blocking> { | ||
| 1053 | type Error = Error; | ||
| 1054 | |||
| 1055 | fn write(&mut self, word: u8) -> core::result::Result<(), nb::Error<Self::Error>> { | ||
| 1056 | embedded_hal_02::serial::Write::write(&mut self.tx, word) | ||
| 1057 | } | ||
| 1058 | |||
| 1059 | fn flush(&mut self) -> core::result::Result<(), nb::Error<Self::Error>> { | ||
| 1060 | embedded_hal_02::serial::Write::flush(&mut self.tx) | ||
| 1061 | } | ||
| 1062 | } | ||
| 1063 | |||
| 1064 | impl embedded_hal_02::blocking::serial::Write<u8> for Lpuart<'_, Blocking> { | ||
| 1065 | type Error = Error; | ||
| 1066 | |||
| 1067 | fn bwrite_all(&mut self, buffer: &[u8]) -> core::result::Result<(), Self::Error> { | ||
| 1068 | self.blocking_write(buffer) | ||
| 1069 | } | ||
| 1070 | |||
| 1071 | fn bflush(&mut self) -> core::result::Result<(), Self::Error> { | ||
| 1072 | self.blocking_flush() | ||
| 1073 | } | ||
| 1074 | } | ||
| 1075 | |||
| 1076 | // ============================================================================ | ||
| 1077 | // EMBEDDED-HAL-NB TRAIT IMPLEMENTATIONS | ||
| 1078 | // ============================================================================ | ||
| 1079 | |||
| 1080 | impl embedded_hal_nb::serial::Error for Error { | ||
| 1081 | fn kind(&self) -> embedded_hal_nb::serial::ErrorKind { | ||
| 1082 | match *self { | ||
| 1083 | Self::Framing => embedded_hal_nb::serial::ErrorKind::FrameFormat, | ||
| 1084 | Self::Overrun => embedded_hal_nb::serial::ErrorKind::Overrun, | ||
| 1085 | Self::Parity => embedded_hal_nb::serial::ErrorKind::Parity, | ||
| 1086 | Self::Noise => embedded_hal_nb::serial::ErrorKind::Noise, | ||
| 1087 | _ => embedded_hal_nb::serial::ErrorKind::Other, | ||
| 1088 | } | ||
| 1089 | } | ||
| 1090 | } | ||
| 1091 | |||
| 1092 | impl embedded_hal_nb::serial::ErrorType for LpuartRx<'_, Blocking> { | ||
| 1093 | type Error = Error; | ||
| 1094 | } | ||
| 1095 | |||
| 1096 | impl embedded_hal_nb::serial::ErrorType for LpuartTx<'_, Blocking> { | ||
| 1097 | type Error = Error; | ||
| 1098 | } | ||
| 1099 | |||
| 1100 | impl embedded_hal_nb::serial::ErrorType for Lpuart<'_, Blocking> { | ||
| 1101 | type Error = Error; | ||
| 1102 | } | ||
| 1103 | |||
| 1104 | impl embedded_hal_nb::serial::Read for LpuartRx<'_, Blocking> { | ||
| 1105 | fn read(&mut self) -> nb::Result<u8, Self::Error> { | ||
| 1106 | let mut buf = [0; 1]; | ||
| 1107 | match self.read(&mut buf) { | ||
| 1108 | Ok(_) => Ok(buf[0]), | ||
| 1109 | Err(Error::RxFifoEmpty) => Err(nb::Error::WouldBlock), | ||
| 1110 | Err(e) => Err(nb::Error::Other(e)), | ||
| 1111 | } | ||
| 1112 | } | ||
| 1113 | } | ||
| 1114 | |||
| 1115 | impl embedded_hal_nb::serial::Write for LpuartTx<'_, Blocking> { | ||
| 1116 | fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { | ||
| 1117 | match self.write(&[word]) { | ||
| 1118 | Ok(_) => Ok(()), | ||
| 1119 | Err(Error::TxFifoFull) => Err(nb::Error::WouldBlock), | ||
| 1120 | Err(e) => Err(nb::Error::Other(e)), | ||
| 1121 | } | ||
| 1122 | } | ||
| 1123 | |||
| 1124 | fn flush(&mut self) -> nb::Result<(), Self::Error> { | ||
| 1125 | match self.flush() { | ||
| 1126 | Ok(_) => Ok(()), | ||
| 1127 | Err(Error::TxBusy) => Err(nb::Error::WouldBlock), | ||
| 1128 | Err(e) => Err(nb::Error::Other(e)), | ||
| 1129 | } | ||
| 1130 | } | ||
| 1131 | } | ||
| 1132 | |||
| 1133 | impl embedded_hal_nb::serial::Read for Lpuart<'_, Blocking> { | ||
| 1134 | fn read(&mut self) -> nb::Result<u8, Self::Error> { | ||
| 1135 | embedded_hal_nb::serial::Read::read(&mut self.rx) | ||
| 1136 | } | ||
| 1137 | } | ||
| 1138 | |||
| 1139 | impl embedded_hal_nb::serial::Write for Lpuart<'_, Blocking> { | ||
| 1140 | fn write(&mut self, char: u8) -> nb::Result<(), Self::Error> { | ||
| 1141 | embedded_hal_nb::serial::Write::write(&mut self.tx, char) | ||
| 1142 | } | ||
| 1143 | |||
| 1144 | fn flush(&mut self) -> nb::Result<(), Self::Error> { | ||
| 1145 | embedded_hal_nb::serial::Write::flush(&mut self.tx) | ||
| 1146 | } | ||
| 1147 | } | ||
| 1148 | |||
| 1149 | // ============================================================================ | ||
| 1150 | // EMBEDDED-IO TRAIT IMPLEMENTATIONS | ||
| 1151 | // ============================================================================ | ||
| 1152 | |||
| 1153 | impl embedded_io::Error for Error { | ||
| 1154 | fn kind(&self) -> embedded_io::ErrorKind { | ||
| 1155 | embedded_io::ErrorKind::Other | ||
| 1156 | } | ||
| 1157 | } | ||
| 1158 | |||
| 1159 | impl embedded_io::ErrorType for LpuartRx<'_, Blocking> { | ||
| 1160 | type Error = Error; | ||
| 1161 | } | ||
| 1162 | |||
| 1163 | impl embedded_io::ErrorType for LpuartTx<'_, Blocking> { | ||
| 1164 | type Error = Error; | ||
| 1165 | } | ||
| 1166 | |||
| 1167 | impl embedded_io::ErrorType for Lpuart<'_, Blocking> { | ||
| 1168 | type Error = Error; | ||
| 1169 | } | ||
| 1170 | |||
| 1171 | impl embedded_io::Read for LpuartRx<'_, Blocking> { | ||
| 1172 | fn read(&mut self, buf: &mut [u8]) -> core::result::Result<usize, Self::Error> { | ||
| 1173 | self.blocking_read(buf).map(|_| buf.len()) | ||
| 1174 | } | ||
| 1175 | } | ||
| 1176 | |||
| 1177 | impl embedded_io::Write for LpuartTx<'_, Blocking> { | ||
| 1178 | fn write(&mut self, buf: &[u8]) -> core::result::Result<usize, Self::Error> { | ||
| 1179 | self.blocking_write(buf).map(|_| buf.len()) | ||
| 1180 | } | ||
| 1181 | |||
| 1182 | fn flush(&mut self) -> core::result::Result<(), Self::Error> { | ||
| 1183 | self.blocking_flush() | ||
| 1184 | } | ||
| 1185 | } | ||
| 1186 | |||
| 1187 | impl embedded_io::Read for Lpuart<'_, Blocking> { | ||
| 1188 | fn read(&mut self, buf: &mut [u8]) -> core::result::Result<usize, Self::Error> { | ||
| 1189 | embedded_io::Read::read(&mut self.rx, buf) | ||
| 1190 | } | ||
| 1191 | } | ||
| 1192 | |||
| 1193 | impl embedded_io::Write for Lpuart<'_, Blocking> { | ||
| 1194 | fn write(&mut self, buf: &[u8]) -> core::result::Result<usize, Self::Error> { | ||
| 1195 | embedded_io::Write::write(&mut self.tx, buf) | ||
| 1196 | } | ||
| 1197 | |||
| 1198 | fn flush(&mut self) -> core::result::Result<(), Self::Error> { | ||
| 1199 | embedded_io::Write::flush(&mut self.tx) | ||
| 1200 | } | ||
| 1201 | } | ||
diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 7111536ff..000000000 --- a/src/main.rs +++ /dev/null | |||
| @@ -1,18 +0,0 @@ | |||
| 1 | #![cfg_attr(target_os = "none", no_std)] | ||
| 2 | #![cfg_attr(target_os = "none", no_main)] | ||
| 3 | |||
| 4 | #[cfg(target_os = "none")] | ||
| 5 | mod baremetal; | ||
| 6 | |||
| 7 | #[cfg(not(target_os = "none"))] | ||
| 8 | fn main() { | ||
| 9 | println!("Hello, world!"); | ||
| 10 | } | ||
| 11 | |||
| 12 | #[cfg(test)] | ||
| 13 | mod tests { | ||
| 14 | #[test] | ||
| 15 | fn it_works() { | ||
| 16 | assert_eq!(2 + 2, 4); | ||
| 17 | } | ||
| 18 | } | ||
diff --git a/src/ostimer.rs b/src/ostimer.rs new file mode 100644 index 000000000..a4cab6970 --- /dev/null +++ b/src/ostimer.rs | |||
| @@ -0,0 +1,678 @@ | |||
| 1 | //! # OSTIMER Driver with Robustness Features | ||
| 2 | //! | ||
| 3 | //! This module provides an async timer driver for the NXP MCXA276 OSTIMER peripheral | ||
| 4 | //! with protection against race conditions and timer rollover issues. | ||
| 5 | //! | ||
| 6 | //! ## Features | ||
| 7 | //! | ||
| 8 | //! - Async timing with embassy-time integration | ||
| 9 | //! - Gray code counter handling (42-bit counter) | ||
| 10 | //! - Interrupt-driven wakeups | ||
| 11 | //! - Configurable interrupt priority | ||
| 12 | //! - **Race condition protection**: Critical sections and atomic operations | ||
| 13 | //! - **Timer rollover handling**: Bounds checking and rollover prevention | ||
| 14 | //! | ||
| 15 | //! ## Clock Frequency Configuration | ||
| 16 | //! | ||
| 17 | //! The OSTIMER frequency depends on your system's clock configuration. You must provide | ||
| 18 | //! the actual frequency when calling `time_driver::init()`. | ||
| 19 | //! | ||
| 20 | //! ## Race Condition Protection | ||
| 21 | //! - Critical sections in interrupt handlers prevent concurrent access | ||
| 22 | //! - Atomic register operations with memory barriers | ||
| 23 | //! - Proper interrupt flag clearing and validation | ||
| 24 | //! | ||
| 25 | //! ## Timer Rollover Handling | ||
| 26 | //! - Bounds checking prevents scheduling beyond timer capacity | ||
| 27 | //! - Immediate wake for timestamps that would cause rollover issues | ||
| 28 | #![allow(dead_code)] | ||
| 29 | |||
| 30 | use core::sync::atomic::{AtomicBool, Ordering}; | ||
| 31 | |||
| 32 | use crate::interrupt::InterruptExt; | ||
| 33 | use crate::pac; | ||
| 34 | |||
| 35 | // PAC defines the shared RegisterBlock under `ostimer0`. | ||
| 36 | type Regs = pac::ostimer0::RegisterBlock; | ||
| 37 | |||
| 38 | // OSTIMER EVTIMER register layout constants | ||
| 39 | /// Total width of the EVTIMER counter in bits (42 bits total) | ||
| 40 | const EVTIMER_TOTAL_BITS: u32 = 42; | ||
| 41 | /// Width of the low part of EVTIMER (bits 31:0) | ||
| 42 | const EVTIMER_LO_BITS: u32 = 32; | ||
| 43 | /// Width of the high part of EVTIMER (bits 41:32) | ||
| 44 | const EVTIMER_HI_BITS: u32 = 10; | ||
| 45 | /// Bit position where high part starts in the combined 64-bit value | ||
| 46 | const EVTIMER_HI_SHIFT: u32 = 32; | ||
| 47 | |||
| 48 | /// Bit mask for the high part of EVTIMER | ||
| 49 | const EVTIMER_HI_MASK: u16 = (1 << EVTIMER_HI_BITS) - 1; | ||
| 50 | |||
| 51 | /// Maximum value for MATCH_L register (32-bit) | ||
| 52 | const MATCH_L_MAX: u32 = u32::MAX; | ||
| 53 | /// Maximum value for MATCH_H register (10-bit) | ||
| 54 | const MATCH_H_MAX: u16 = EVTIMER_HI_MASK; | ||
| 55 | |||
| 56 | /// Bit mask for extracting the low 32 bits from a 64-bit value | ||
| 57 | const LOW_32_BIT_MASK: u64 = u32::MAX as u64; | ||
| 58 | |||
| 59 | /// Gray code conversion bit shifts (most significant to least) | ||
| 60 | const GRAY_CONVERSION_SHIFTS: [u32; 6] = [32, 16, 8, 4, 2, 1]; | ||
| 61 | |||
| 62 | /// Maximum timer value before rollover (2^42 - 1 ticks) | ||
| 63 | /// Actual rollover time depends on the configured clock frequency | ||
| 64 | const TIMER_MAX_VALUE: u64 = (1u64 << EVTIMER_TOTAL_BITS) - 1; | ||
| 65 | |||
| 66 | /// Threshold for detecting timer rollover in comparisons (1 second at 1MHz) | ||
| 67 | const TIMER_ROLLOVER_THRESHOLD: u64 = 1_000_000; | ||
| 68 | |||
| 69 | /// Common default interrupt priority for OSTIMER | ||
| 70 | const DEFAULT_INTERRUPT_PRIORITY: u8 = 3; | ||
| 71 | |||
| 72 | // Global alarm state for interrupt handling | ||
| 73 | static ALARM_ACTIVE: AtomicBool = AtomicBool::new(false); | ||
| 74 | static mut ALARM_CALLBACK: Option<fn()> = None; | ||
| 75 | static mut ALARM_FLAG: Option<*const AtomicBool> = None; | ||
| 76 | static mut ALARM_TARGET_TIME: u64 = 0; | ||
| 77 | |||
| 78 | /// Number of tight spin iterations between elapsed time checks while waiting for MATCH writes to return to the idle (0) state. | ||
| 79 | const MATCH_WRITE_READY_SPINS: usize = 512; | ||
| 80 | /// Maximum time (in OSTIMER ticks) to wait for MATCH registers to become writable (~5 ms at 1 MHz). | ||
| 81 | const MATCH_WRITE_READY_TIMEOUT_TICKS: u64 = 5_000; | ||
| 82 | /// Short stabilization delay executed after toggling the MRCC reset line to let the OSTIMER bus interface settle. | ||
| 83 | const RESET_STABILIZE_SPINS: usize = 512; | ||
| 84 | |||
| 85 | pub(super) fn wait_for_match_write_ready(r: &Regs) -> bool { | ||
| 86 | let start = now_ticks_read(); | ||
| 87 | let mut spin_budget = 0usize; | ||
| 88 | |||
| 89 | loop { | ||
| 90 | if r.osevent_ctrl().read().match_wr_rdy().bit_is_clear() { | ||
| 91 | return true; | ||
| 92 | } | ||
| 93 | |||
| 94 | cortex_m::asm::nop(); | ||
| 95 | spin_budget += 1; | ||
| 96 | |||
| 97 | if spin_budget >= MATCH_WRITE_READY_SPINS { | ||
| 98 | spin_budget = 0; | ||
| 99 | |||
| 100 | let elapsed = now_ticks_read().wrapping_sub(start); | ||
| 101 | if elapsed >= MATCH_WRITE_READY_TIMEOUT_TICKS { | ||
| 102 | return false; | ||
| 103 | } | ||
| 104 | } | ||
| 105 | } | ||
| 106 | } | ||
| 107 | |||
| 108 | pub(super) fn wait_for_match_write_complete(r: &Regs) -> bool { | ||
| 109 | let start = now_ticks_read(); | ||
| 110 | let mut spin_budget = 0usize; | ||
| 111 | |||
| 112 | loop { | ||
| 113 | if r.osevent_ctrl().read().match_wr_rdy().bit_is_clear() { | ||
| 114 | return true; | ||
| 115 | } | ||
| 116 | |||
| 117 | cortex_m::asm::nop(); | ||
| 118 | spin_budget += 1; | ||
| 119 | |||
| 120 | if spin_budget >= MATCH_WRITE_READY_SPINS { | ||
| 121 | spin_budget = 0; | ||
| 122 | |||
| 123 | let elapsed = now_ticks_read().wrapping_sub(start); | ||
| 124 | if elapsed >= MATCH_WRITE_READY_TIMEOUT_TICKS { | ||
| 125 | return false; | ||
| 126 | } | ||
| 127 | } | ||
| 128 | } | ||
| 129 | } | ||
| 130 | |||
| 131 | fn prime_match_registers(r: &Regs) { | ||
| 132 | // Disable the interrupt, clear any pending flag, then wait until the MATCH registers are writable. | ||
| 133 | r.osevent_ctrl() | ||
| 134 | .write(|w| w.ostimer_intrflag().clear_bit_by_one().ostimer_intena().clear_bit()); | ||
| 135 | |||
| 136 | if wait_for_match_write_ready(r) { | ||
| 137 | r.match_l().write(|w| unsafe { w.match_value().bits(MATCH_L_MAX) }); | ||
| 138 | r.match_h().write(|w| unsafe { w.match_value().bits(MATCH_H_MAX) }); | ||
| 139 | let _ = wait_for_match_write_complete(r); | ||
| 140 | } | ||
| 141 | } | ||
| 142 | |||
| 143 | /// Single-shot alarm functionality for OSTIMER | ||
| 144 | pub struct Alarm<'d> { | ||
| 145 | /// Whether the alarm is currently active | ||
| 146 | active: AtomicBool, | ||
| 147 | /// Callback to execute when alarm expires (optional) | ||
| 148 | callback: Option<fn()>, | ||
| 149 | /// Flag that gets set when alarm expires (optional) | ||
| 150 | flag: Option<&'d AtomicBool>, | ||
| 151 | _phantom: core::marker::PhantomData<&'d mut ()>, | ||
| 152 | } | ||
| 153 | |||
| 154 | impl<'d> Alarm<'d> { | ||
| 155 | /// Create a new alarm instance | ||
| 156 | pub fn new() -> Self { | ||
| 157 | Self { | ||
| 158 | active: AtomicBool::new(false), | ||
| 159 | callback: None, | ||
| 160 | flag: None, | ||
| 161 | _phantom: core::marker::PhantomData, | ||
| 162 | } | ||
| 163 | } | ||
| 164 | |||
| 165 | /// Set a callback that will be executed when the alarm expires | ||
| 166 | /// Note: Due to interrupt handler constraints, callbacks must be static function pointers | ||
| 167 | pub fn with_callback(mut self, callback: fn()) -> Self { | ||
| 168 | self.callback = Some(callback); | ||
| 169 | self | ||
| 170 | } | ||
| 171 | |||
| 172 | /// Set a flag that will be set to true when the alarm expires | ||
| 173 | pub fn with_flag(mut self, flag: &'d AtomicBool) -> Self { | ||
| 174 | self.flag = Some(flag); | ||
| 175 | self | ||
| 176 | } | ||
| 177 | |||
| 178 | /// Check if the alarm is currently active | ||
| 179 | pub fn is_active(&self) -> bool { | ||
| 180 | self.active.load(Ordering::Acquire) | ||
| 181 | } | ||
| 182 | |||
| 183 | /// Cancel the alarm if it's active | ||
| 184 | pub fn cancel(&self) { | ||
| 185 | self.active.store(false, Ordering::Release); | ||
| 186 | } | ||
| 187 | } | ||
| 188 | |||
| 189 | /// Configuration for Ostimer::new() | ||
| 190 | #[derive(Copy, Clone)] | ||
| 191 | pub struct Config { | ||
| 192 | /// Initialize MATCH registers to their max values and mask/clear the interrupt flag. | ||
| 193 | pub init_match_max: bool, | ||
| 194 | /// OSTIMER clock frequency in Hz (must match the actual hardware clock) | ||
| 195 | pub clock_frequency_hz: u64, | ||
| 196 | } | ||
| 197 | |||
| 198 | impl Default for Config { | ||
| 199 | fn default() -> Self { | ||
| 200 | Self { | ||
| 201 | init_match_max: true, | ||
| 202 | // Default to 1MHz - user should override this with actual frequency | ||
| 203 | clock_frequency_hz: 1_000_000, | ||
| 204 | } | ||
| 205 | } | ||
| 206 | } | ||
| 207 | |||
| 208 | /// OSTIMER peripheral instance | ||
| 209 | pub struct Ostimer<'d, I: Instance> { | ||
| 210 | _inst: core::marker::PhantomData<I>, | ||
| 211 | clock_frequency_hz: u64, | ||
| 212 | _phantom: core::marker::PhantomData<&'d mut ()>, | ||
| 213 | } | ||
| 214 | |||
| 215 | impl<'d, I: Instance> Ostimer<'d, I> { | ||
| 216 | /// Construct OSTIMER handle. | ||
| 217 | /// Requires clocks for the instance to be enabled by the board before calling. | ||
| 218 | /// Does not enable NVIC or INTENA; use time_driver::init() for async operation. | ||
| 219 | pub fn new(_inst: impl Instance, cfg: Config, _p: &'d crate::pac::Peripherals) -> Self { | ||
| 220 | assert!(cfg.clock_frequency_hz > 0, "OSTIMER frequency must be greater than 0"); | ||
| 221 | |||
| 222 | if cfg.init_match_max { | ||
| 223 | let r: &Regs = unsafe { &*I::ptr() }; | ||
| 224 | // Mask INTENA, clear pending flag, and set MATCH to max so no spurious IRQ fires. | ||
| 225 | prime_match_registers(r); | ||
| 226 | } | ||
| 227 | |||
| 228 | Self { | ||
| 229 | _inst: core::marker::PhantomData, | ||
| 230 | clock_frequency_hz: cfg.clock_frequency_hz, | ||
| 231 | _phantom: core::marker::PhantomData, | ||
| 232 | } | ||
| 233 | } | ||
| 234 | |||
| 235 | /// Get the configured clock frequency in Hz | ||
| 236 | pub fn clock_frequency_hz(&self) -> u64 { | ||
| 237 | self.clock_frequency_hz | ||
| 238 | } | ||
| 239 | |||
| 240 | /// Read the current timer counter value in timer ticks | ||
| 241 | /// | ||
| 242 | /// # Returns | ||
| 243 | /// Current timer counter value as a 64-bit unsigned integer | ||
| 244 | pub fn now(&self) -> u64 { | ||
| 245 | now_ticks_read() | ||
| 246 | } | ||
| 247 | |||
| 248 | /// Reset the timer counter to zero | ||
| 249 | /// | ||
| 250 | /// This performs a hardware reset of the OSTIMER peripheral, which will reset | ||
| 251 | /// the counter to zero and clear any pending interrupts. Note that this will | ||
| 252 | /// affect all timer operations including embassy-time. | ||
| 253 | /// | ||
| 254 | /// # Safety | ||
| 255 | /// This operation will reset the entire OSTIMER peripheral. Any active alarms | ||
| 256 | /// or time_driver operations will be disrupted. Use with caution. | ||
| 257 | pub fn reset(&self, peripherals: &crate::pac::Peripherals) { | ||
| 258 | critical_section::with(|_| { | ||
| 259 | let r: &Regs = unsafe { &*I::ptr() }; | ||
| 260 | |||
| 261 | // Mask the peripheral interrupt flag before we toggle the reset line so that | ||
| 262 | // no new NVIC activity races with the reset sequence. | ||
| 263 | r.osevent_ctrl() | ||
| 264 | .write(|w| w.ostimer_intrflag().clear_bit_by_one().ostimer_intena().clear_bit()); | ||
| 265 | |||
| 266 | unsafe { | ||
| 267 | crate::reset::assert::<crate::reset::line::Ostimer0>(peripherals); | ||
| 268 | } | ||
| 269 | |||
| 270 | for _ in 0..RESET_STABILIZE_SPINS { | ||
| 271 | cortex_m::asm::nop(); | ||
| 272 | } | ||
| 273 | |||
| 274 | unsafe { | ||
| 275 | crate::reset::release::<crate::reset::line::Ostimer0>(peripherals); | ||
| 276 | } | ||
| 277 | |||
| 278 | while !<crate::reset::line::Ostimer0 as crate::reset::ResetLine>::is_released(&peripherals.mrcc0) { | ||
| 279 | cortex_m::asm::nop(); | ||
| 280 | } | ||
| 281 | |||
| 282 | for _ in 0..RESET_STABILIZE_SPINS { | ||
| 283 | cortex_m::asm::nop(); | ||
| 284 | } | ||
| 285 | |||
| 286 | // Clear alarm bookkeeping before re-arming MATCH registers. | ||
| 287 | ALARM_ACTIVE.store(false, Ordering::Release); | ||
| 288 | unsafe { | ||
| 289 | ALARM_TARGET_TIME = 0; | ||
| 290 | ALARM_CALLBACK = None; | ||
| 291 | ALARM_FLAG = None; | ||
| 292 | } | ||
| 293 | |||
| 294 | prime_match_registers(r); | ||
| 295 | }); | ||
| 296 | |||
| 297 | // Ensure no stale OS_EVENT request remains pending after the reset sequence. | ||
| 298 | crate::interrupt::OS_EVENT.unpend(); | ||
| 299 | } | ||
| 300 | |||
| 301 | /// Schedule a single-shot alarm to expire after the specified delay in microseconds | ||
| 302 | /// | ||
| 303 | /// # Parameters | ||
| 304 | /// * `alarm` - The alarm instance to schedule | ||
| 305 | /// * `delay_us` - Delay in microseconds from now | ||
| 306 | /// | ||
| 307 | /// # Returns | ||
| 308 | /// `true` if the alarm was scheduled successfully, `false` if it would exceed timer capacity | ||
| 309 | pub fn schedule_alarm_delay(&self, alarm: &Alarm, delay_us: u64) -> bool { | ||
| 310 | let delay_ticks = (delay_us * self.clock_frequency_hz) / 1_000_000; | ||
| 311 | let target_time = now_ticks_read() + delay_ticks; | ||
| 312 | self.schedule_alarm_at(alarm, target_time) | ||
| 313 | } | ||
| 314 | |||
| 315 | /// Schedule a single-shot alarm to expire at the specified absolute time in timer ticks | ||
| 316 | /// | ||
| 317 | /// # Parameters | ||
| 318 | /// * `alarm` - The alarm instance to schedule | ||
| 319 | /// * `target_ticks` - Absolute time in timer ticks when the alarm should expire | ||
| 320 | /// | ||
| 321 | /// # Returns | ||
| 322 | /// `true` if the alarm was scheduled successfully, `false` if it would exceed timer capacity | ||
| 323 | pub fn schedule_alarm_at(&self, alarm: &Alarm, target_ticks: u64) -> bool { | ||
| 324 | let now = now_ticks_read(); | ||
| 325 | |||
| 326 | // Check if target time is in the past | ||
| 327 | if target_ticks <= now { | ||
| 328 | // Execute callback immediately if alarm was supposed to be active | ||
| 329 | if alarm.active.load(Ordering::Acquire) { | ||
| 330 | alarm.active.store(false, Ordering::Release); | ||
| 331 | if let Some(callback) = alarm.callback { | ||
| 332 | callback(); | ||
| 333 | } | ||
| 334 | if let Some(flag) = &alarm.flag { | ||
| 335 | flag.store(true, Ordering::Release); | ||
| 336 | } | ||
| 337 | } | ||
| 338 | return true; | ||
| 339 | } | ||
| 340 | |||
| 341 | // Check for timer rollover | ||
| 342 | let max_future = now + TIMER_MAX_VALUE; | ||
| 343 | if target_ticks > max_future { | ||
| 344 | return false; // Would exceed timer capacity | ||
| 345 | } | ||
| 346 | |||
| 347 | // Program the timer | ||
| 348 | let r: &Regs = unsafe { &*I::ptr() }; | ||
| 349 | |||
| 350 | critical_section::with(|_| { | ||
| 351 | // Disable interrupt and clear flag | ||
| 352 | r.osevent_ctrl() | ||
| 353 | .write(|w| w.ostimer_intrflag().clear_bit_by_one().ostimer_intena().clear_bit()); | ||
| 354 | |||
| 355 | if !wait_for_match_write_ready(r) { | ||
| 356 | prime_match_registers(r); | ||
| 357 | |||
| 358 | if !wait_for_match_write_ready(r) { | ||
| 359 | alarm.active.store(false, Ordering::Release); | ||
| 360 | ALARM_ACTIVE.store(false, Ordering::Release); | ||
| 361 | unsafe { | ||
| 362 | ALARM_TARGET_TIME = 0; | ||
| 363 | ALARM_CALLBACK = None; | ||
| 364 | ALARM_FLAG = None; | ||
| 365 | } | ||
| 366 | return false; | ||
| 367 | } | ||
| 368 | } | ||
| 369 | |||
| 370 | // Mark alarm as active now that we know the MATCH registers are writable | ||
| 371 | alarm.active.store(true, Ordering::Release); | ||
| 372 | |||
| 373 | // Set global alarm state for interrupt handler | ||
| 374 | ALARM_ACTIVE.store(true, Ordering::Release); | ||
| 375 | unsafe { | ||
| 376 | ALARM_TARGET_TIME = target_ticks; | ||
| 377 | ALARM_CALLBACK = alarm.callback; | ||
| 378 | ALARM_FLAG = alarm.flag.map(|f| f as *const AtomicBool); | ||
| 379 | } | ||
| 380 | |||
| 381 | // Program MATCH registers (Gray-coded) | ||
| 382 | let gray = bin_to_gray(target_ticks); | ||
| 383 | let l = (gray & LOW_32_BIT_MASK) as u32; | ||
| 384 | let h = (((gray >> EVTIMER_HI_SHIFT) as u16) & EVTIMER_HI_MASK) as u16; | ||
| 385 | |||
| 386 | r.match_l().write(|w| unsafe { w.match_value().bits(l) }); | ||
| 387 | r.match_h().write(|w| unsafe { w.match_value().bits(h) }); | ||
| 388 | |||
| 389 | if !wait_for_match_write_complete(r) { | ||
| 390 | alarm.active.store(false, Ordering::Release); | ||
| 391 | ALARM_ACTIVE.store(false, Ordering::Release); | ||
| 392 | unsafe { | ||
| 393 | ALARM_TARGET_TIME = 0; | ||
| 394 | ALARM_CALLBACK = None; | ||
| 395 | ALARM_FLAG = None; | ||
| 396 | } | ||
| 397 | return false; | ||
| 398 | } | ||
| 399 | |||
| 400 | let now_after_program = now_ticks_read(); | ||
| 401 | let intrflag_set = r.osevent_ctrl().read().ostimer_intrflag().bit_is_set(); | ||
| 402 | if now_after_program >= target_ticks && !intrflag_set { | ||
| 403 | alarm.active.store(false, Ordering::Release); | ||
| 404 | ALARM_ACTIVE.store(false, Ordering::Release); | ||
| 405 | unsafe { | ||
| 406 | ALARM_TARGET_TIME = 0; | ||
| 407 | ALARM_CALLBACK = None; | ||
| 408 | ALARM_FLAG = None; | ||
| 409 | } | ||
| 410 | return false; | ||
| 411 | } | ||
| 412 | |||
| 413 | // Enable interrupt | ||
| 414 | r.osevent_ctrl().write(|w| w.ostimer_intena().set_bit()); | ||
| 415 | |||
| 416 | true | ||
| 417 | }) | ||
| 418 | } | ||
| 419 | |||
| 420 | /// Cancel any active alarm | ||
| 421 | pub fn cancel_alarm(&self, alarm: &Alarm) { | ||
| 422 | critical_section::with(|_| { | ||
| 423 | alarm.cancel(); | ||
| 424 | |||
| 425 | // Clear global alarm state | ||
| 426 | ALARM_ACTIVE.store(false, Ordering::Release); | ||
| 427 | unsafe { ALARM_TARGET_TIME = 0 }; | ||
| 428 | |||
| 429 | // Reset MATCH registers to maximum values to prevent spurious interrupts | ||
| 430 | let r: &Regs = unsafe { &*I::ptr() }; | ||
| 431 | prime_match_registers(r); | ||
| 432 | }); | ||
| 433 | } | ||
| 434 | |||
| 435 | /// Check if an alarm has expired (call this from your interrupt handler) | ||
| 436 | /// Returns true if the alarm was active and has now expired | ||
| 437 | pub fn check_alarm_expired(&self, alarm: &Alarm) -> bool { | ||
| 438 | if alarm.active.load(Ordering::Acquire) { | ||
| 439 | alarm.active.store(false, Ordering::Release); | ||
| 440 | |||
| 441 | // Execute callback | ||
| 442 | if let Some(callback) = alarm.callback { | ||
| 443 | callback(); | ||
| 444 | } | ||
| 445 | |||
| 446 | // Set flag | ||
| 447 | if let Some(flag) = &alarm.flag { | ||
| 448 | flag.store(true, Ordering::Release); | ||
| 449 | } | ||
| 450 | |||
| 451 | true | ||
| 452 | } else { | ||
| 453 | false | ||
| 454 | } | ||
| 455 | } | ||
| 456 | } | ||
| 457 | |||
| 458 | /// Read current EVTIMER (Gray-coded) and convert to binary ticks. | ||
| 459 | #[inline(always)] | ||
| 460 | fn now_ticks_read() -> u64 { | ||
| 461 | let r: &Regs = unsafe { &*pac::Ostimer0::ptr() }; | ||
| 462 | |||
| 463 | // Read high then low to minimize incoherent snapshots | ||
| 464 | let hi = (r.evtimerh().read().evtimer_count_value().bits() as u64) & (EVTIMER_HI_MASK as u64); | ||
| 465 | let lo = r.evtimerl().read().evtimer_count_value().bits() as u64; | ||
| 466 | |||
| 467 | // Combine and convert from Gray code to binary | ||
| 468 | let gray = lo | (hi << EVTIMER_HI_SHIFT); | ||
| 469 | gray_to_bin(gray) | ||
| 470 | } | ||
| 471 | |||
| 472 | // Instance trait like other drivers, providing a PAC pointer for this OSTIMER instance | ||
| 473 | pub trait Instance { | ||
| 474 | fn ptr() -> *const Regs; | ||
| 475 | } | ||
| 476 | |||
| 477 | // Token for OSTIMER0 provided by embassy-hal-internal peripherals macro. | ||
| 478 | pub type Ostimer0 = crate::peripherals::OSTIMER0; | ||
| 479 | |||
| 480 | impl Instance for crate::peripherals::OSTIMER0 { | ||
| 481 | #[inline(always)] | ||
| 482 | fn ptr() -> *const Regs { | ||
| 483 | pac::Ostimer0::ptr() | ||
| 484 | } | ||
| 485 | } | ||
| 486 | |||
| 487 | // Also implement Instance for the Peri wrapper type | ||
| 488 | impl Instance for embassy_hal_internal::Peri<'_, crate::peripherals::OSTIMER0> { | ||
| 489 | #[inline(always)] | ||
| 490 | fn ptr() -> *const Regs { | ||
| 491 | pac::Ostimer0::ptr() | ||
| 492 | } | ||
| 493 | } | ||
| 494 | |||
| 495 | #[inline(always)] | ||
| 496 | fn bin_to_gray(x: u64) -> u64 { | ||
| 497 | x ^ (x >> 1) | ||
| 498 | } | ||
| 499 | |||
| 500 | #[inline(always)] | ||
| 501 | fn gray_to_bin(gray: u64) -> u64 { | ||
| 502 | // More efficient iterative conversion using predefined shifts | ||
| 503 | let mut bin = gray; | ||
| 504 | for &shift in &GRAY_CONVERSION_SHIFTS { | ||
| 505 | bin ^= bin >> shift; | ||
| 506 | } | ||
| 507 | bin | ||
| 508 | } | ||
| 509 | |||
| 510 | pub mod time_driver { | ||
| 511 | use core::sync::atomic::Ordering; | ||
| 512 | use core::task::Waker; | ||
| 513 | |||
| 514 | use embassy_sync::waitqueue::AtomicWaker; | ||
| 515 | use embassy_time_driver as etd; | ||
| 516 | |||
| 517 | use super::{ | ||
| 518 | bin_to_gray, now_ticks_read, Regs, ALARM_ACTIVE, ALARM_CALLBACK, ALARM_FLAG, ALARM_TARGET_TIME, | ||
| 519 | EVTIMER_HI_MASK, EVTIMER_HI_SHIFT, LOW_32_BIT_MASK, | ||
| 520 | }; | ||
| 521 | use crate::pac; | ||
| 522 | pub struct Driver; | ||
| 523 | static TIMER_WAKER: AtomicWaker = AtomicWaker::new(); | ||
| 524 | |||
| 525 | impl etd::Driver for Driver { | ||
| 526 | fn now(&self) -> u64 { | ||
| 527 | // Use the hardware counter (frequency configured in init) | ||
| 528 | super::now_ticks_read() | ||
| 529 | } | ||
| 530 | |||
| 531 | fn schedule_wake(&self, timestamp: u64, waker: &Waker) { | ||
| 532 | let now = self.now(); | ||
| 533 | |||
| 534 | // If timestamp is in the past or very close to now, wake immediately | ||
| 535 | if timestamp <= now { | ||
| 536 | waker.wake_by_ref(); | ||
| 537 | return; | ||
| 538 | } | ||
| 539 | |||
| 540 | // Prevent scheduling too far in the future (beyond timer rollover) | ||
| 541 | // This prevents wraparound issues | ||
| 542 | let max_future = now + super::TIMER_MAX_VALUE; | ||
| 543 | if timestamp > max_future { | ||
| 544 | // For very long timeouts, wake immediately to avoid rollover issues | ||
| 545 | waker.wake_by_ref(); | ||
| 546 | return; | ||
| 547 | } | ||
| 548 | |||
| 549 | // Register the waker first so any immediate wake below is observed by the executor. | ||
| 550 | TIMER_WAKER.register(waker); | ||
| 551 | |||
| 552 | let r: &Regs = unsafe { &*pac::Ostimer0::ptr() }; | ||
| 553 | |||
| 554 | critical_section::with(|_| { | ||
| 555 | // Mask INTENA and clear flag | ||
| 556 | r.osevent_ctrl() | ||
| 557 | .write(|w| w.ostimer_intrflag().clear_bit_by_one().ostimer_intena().clear_bit()); | ||
| 558 | |||
| 559 | // Read back to ensure W1C took effect on hardware | ||
| 560 | let _ = r.osevent_ctrl().read().ostimer_intrflag().bit(); | ||
| 561 | |||
| 562 | if !super::wait_for_match_write_ready(r) { | ||
| 563 | super::prime_match_registers(r); | ||
| 564 | |||
| 565 | if !super::wait_for_match_write_ready(r) { | ||
| 566 | // If we can't safely program MATCH, wake immediately and leave INTENA masked. | ||
| 567 | waker.wake_by_ref(); | ||
| 568 | return; | ||
| 569 | } | ||
| 570 | } | ||
| 571 | |||
| 572 | // Program MATCH (Gray-coded). Write low then high, then fence. | ||
| 573 | let gray = bin_to_gray(timestamp); | ||
| 574 | let l = (gray & LOW_32_BIT_MASK) as u32; | ||
| 575 | |||
| 576 | let h = (((gray >> EVTIMER_HI_SHIFT) as u16) & EVTIMER_HI_MASK) as u16; | ||
| 577 | |||
| 578 | r.match_l().write(|w| unsafe { w.match_value().bits(l) }); | ||
| 579 | r.match_h().write(|w| unsafe { w.match_value().bits(h) }); | ||
| 580 | |||
| 581 | if !super::wait_for_match_write_complete(r) { | ||
| 582 | waker.wake_by_ref(); | ||
| 583 | return; | ||
| 584 | } | ||
| 585 | |||
| 586 | let now_after_program = super::now_ticks_read(); | ||
| 587 | let intrflag_set = r.osevent_ctrl().read().ostimer_intrflag().bit_is_set(); | ||
| 588 | if now_after_program >= timestamp && !intrflag_set { | ||
| 589 | waker.wake_by_ref(); | ||
| 590 | return; | ||
| 591 | } | ||
| 592 | |||
| 593 | // Enable peripheral interrupt | ||
| 594 | r.osevent_ctrl().write(|w| w.ostimer_intena().set_bit()); | ||
| 595 | }); | ||
| 596 | } | ||
| 597 | } | ||
| 598 | |||
| 599 | /// Install the global embassy-time driver and configure NVIC priority for OS_EVENT. | ||
| 600 | /// | ||
| 601 | /// # Parameters | ||
| 602 | /// * `priority` - Interrupt priority for the OSTIMER interrupt | ||
| 603 | /// * `frequency_hz` - Actual OSTIMER clock frequency in Hz (stored for future use) | ||
| 604 | /// | ||
| 605 | /// Note: The frequency parameter is currently accepted for API compatibility. | ||
| 606 | /// The embassy_time_driver macro handles driver registration automatically. | ||
| 607 | pub fn init(priority: crate::interrupt::Priority, frequency_hz: u64) { | ||
| 608 | // Mask/clear at peripheral and set default MATCH | ||
| 609 | let r: &Regs = unsafe { &*pac::Ostimer0::ptr() }; | ||
| 610 | super::prime_match_registers(r); | ||
| 611 | |||
| 612 | // Configure NVIC for timer operation | ||
| 613 | crate::interrupt::OS_EVENT.configure_for_timer(priority); | ||
| 614 | |||
| 615 | // Note: The embassy_time_driver macro automatically registers the driver | ||
| 616 | // The frequency parameter is accepted for future compatibility | ||
| 617 | let _ = frequency_hz; // Suppress unused parameter warning | ||
| 618 | } | ||
| 619 | |||
| 620 | // Export the global time driver expected by embassy-time | ||
| 621 | embassy_time_driver::time_driver_impl!(static DRIVER: Driver = Driver); | ||
| 622 | |||
| 623 | /// To be called from the OS_EVENT IRQ. | ||
| 624 | pub fn on_interrupt() { | ||
| 625 | let r: &Regs = unsafe { &*pac::Ostimer0::ptr() }; | ||
| 626 | |||
| 627 | // Critical section to prevent races with schedule_wake | ||
| 628 | critical_section::with(|_| { | ||
| 629 | // Check if interrupt is actually pending and handle it atomically | ||
| 630 | if r.osevent_ctrl().read().ostimer_intrflag().bit_is_set() { | ||
| 631 | // Clear flag and disable interrupt atomically | ||
| 632 | r.osevent_ctrl().write(|w| { | ||
| 633 | w.ostimer_intrflag() | ||
| 634 | .clear_bit_by_one() // Write-1-to-clear using safe helper | ||
| 635 | .ostimer_intena() | ||
| 636 | .clear_bit() | ||
| 637 | }); | ||
| 638 | |||
| 639 | // Wake the waiting task | ||
| 640 | TIMER_WAKER.wake(); | ||
| 641 | |||
| 642 | // Handle alarm callback if active and this interrupt is for the alarm | ||
| 643 | if ALARM_ACTIVE.load(Ordering::SeqCst) { | ||
| 644 | let current_time = now_ticks_read(); | ||
| 645 | let target_time = unsafe { ALARM_TARGET_TIME }; | ||
| 646 | |||
| 647 | // Check if current time is close to alarm target time (within 1000 ticks for timing variations) | ||
| 648 | if current_time >= target_time && current_time <= target_time + 1000 { | ||
| 649 | ALARM_ACTIVE.store(false, Ordering::SeqCst); | ||
| 650 | unsafe { ALARM_TARGET_TIME = 0 }; | ||
| 651 | |||
| 652 | // Execute callback if set | ||
| 653 | unsafe { | ||
| 654 | if let Some(callback) = ALARM_CALLBACK { | ||
| 655 | callback(); | ||
| 656 | } | ||
| 657 | } | ||
| 658 | |||
| 659 | // Set flag if provided | ||
| 660 | unsafe { | ||
| 661 | if let Some(flag) = ALARM_FLAG { | ||
| 662 | (*flag).store(true, Ordering::SeqCst); | ||
| 663 | } | ||
| 664 | } | ||
| 665 | } | ||
| 666 | } | ||
| 667 | } | ||
| 668 | }); | ||
| 669 | } | ||
| 670 | |||
| 671 | /// Type-level handler to be used with bind_interrupts! for OS_EVENT. | ||
| 672 | pub struct OsEventHandler; | ||
| 673 | impl crate::interrupt::typelevel::Handler<crate::interrupt::typelevel::OS_EVENT> for OsEventHandler { | ||
| 674 | unsafe fn on_interrupt() { | ||
| 675 | on_interrupt(); | ||
| 676 | } | ||
| 677 | } | ||
| 678 | } | ||
diff --git a/src/pins.rs b/src/pins.rs new file mode 100644 index 000000000..f802568f3 --- /dev/null +++ b/src/pins.rs | |||
| @@ -0,0 +1,123 @@ | |||
| 1 | //! Pin configuration helpers (separate from peripheral drivers). | ||
| 2 | use crate::pac; | ||
| 3 | |||
| 4 | pub unsafe fn configure_uart2_pins_port2() { | ||
| 5 | // P2_2 = LPUART2_TX ALT3, P2_3 = LPUART2_RX ALT3 with pull-up, input enable, high drive, slow slew. | ||
| 6 | let port2 = &*pac::Port2::ptr(); | ||
| 7 | port2.pcr2().write(|w| { | ||
| 8 | w.ps() | ||
| 9 | .ps1() | ||
| 10 | .pe() | ||
| 11 | .pe1() | ||
| 12 | .sre() | ||
| 13 | .sre1() | ||
| 14 | .dse() | ||
| 15 | .dse1() | ||
| 16 | .mux() | ||
| 17 | .mux11() | ||
| 18 | .ibe() | ||
| 19 | .ibe1() | ||
| 20 | }); | ||
| 21 | port2.pcr3().write(|w| { | ||
| 22 | w.ps() | ||
| 23 | .ps1() | ||
| 24 | .pe() | ||
| 25 | .pe1() | ||
| 26 | .sre() | ||
| 27 | .sre1() | ||
| 28 | .dse() | ||
| 29 | .dse1() | ||
| 30 | .mux() | ||
| 31 | .mux11() | ||
| 32 | .ibe() | ||
| 33 | .ibe1() | ||
| 34 | }); | ||
| 35 | core::arch::asm!("dsb sy; isb sy"); | ||
| 36 | } | ||
| 37 | |||
| 38 | pub unsafe fn configure_adc_pins() { | ||
| 39 | // P1_10 = ADC1_A8 | ||
| 40 | let port1 = &*pac::Port1::ptr(); | ||
| 41 | port1.pcr10().write(|w| { | ||
| 42 | w.ps() | ||
| 43 | .ps0() | ||
| 44 | .pe() | ||
| 45 | .pe0() | ||
| 46 | .sre() | ||
| 47 | .sre0() | ||
| 48 | .ode() | ||
| 49 | .ode0() | ||
| 50 | .dse() | ||
| 51 | .dse0() | ||
| 52 | .mux() | ||
| 53 | .mux00() | ||
| 54 | .ibe() | ||
| 55 | .ibe0() | ||
| 56 | .inv() | ||
| 57 | .inv0() | ||
| 58 | .lk() | ||
| 59 | .lk0() | ||
| 60 | }); | ||
| 61 | core::arch::asm!("dsb sy; isb sy"); | ||
| 62 | } | ||
| 63 | |||
| 64 | /// Configure a pin for a specific mux alternative. | ||
| 65 | /// | ||
| 66 | /// # Arguments | ||
| 67 | /// * `port` - Port number (0-4) | ||
| 68 | /// * `pin` - Pin number (varies by port: PORT0=0-7, PORT1=0-19, PORT2=0-26, PORT3=0-31, PORT4=0-7) | ||
| 69 | /// * `mux` - Mux alternative (0-15, where 0 = GPIO, 1-15 = other functions) | ||
| 70 | pub unsafe fn set_pin_mux(port: u8, pin: u8, mux: u8) { | ||
| 71 | // Validate mux value (0-15) | ||
| 72 | if mux > 15 { | ||
| 73 | panic!("Invalid mux value: {}, must be 0-15", mux); | ||
| 74 | } | ||
| 75 | |||
| 76 | // Validate pin number based on port | ||
| 77 | let max_pin = match port { | ||
| 78 | 0 => 7, // PORT0: pins 0-7 | ||
| 79 | 1 => 19, // PORT1: pins 0-19 | ||
| 80 | 2 => 26, // PORT2: pins 0-26 | ||
| 81 | 3 => 31, // PORT3: pins 0-31 | ||
| 82 | 4 => 7, // PORT4: pins 0-7 | ||
| 83 | _ => panic!("Unsupported GPIO port: {}", port), | ||
| 84 | }; | ||
| 85 | |||
| 86 | if pin > max_pin { | ||
| 87 | panic!("Invalid pin {} for PORT{}, max pin is {}", pin, port, max_pin); | ||
| 88 | } | ||
| 89 | |||
| 90 | // Get the base address for the port | ||
| 91 | let port_base: *mut u32 = match port { | ||
| 92 | 0 => pac::Port0::ptr() as *mut u32, | ||
| 93 | 1 => pac::Port1::ptr() as *mut u32, | ||
| 94 | 2 => pac::Port2::ptr() as *mut u32, | ||
| 95 | 3 => pac::Port3::ptr() as *mut u32, | ||
| 96 | 4 => pac::Port4::ptr() as *mut u32, | ||
| 97 | _ => panic!("Unsupported GPIO port: {}", port), | ||
| 98 | }; | ||
| 99 | |||
| 100 | // PCR registers are 4 bytes apart, starting at offset 0 for PCR0 | ||
| 101 | let pcr_addr = port_base.add(pin as usize); | ||
| 102 | |||
| 103 | // Read current PCR value | ||
| 104 | let current_val = pcr_addr.read_volatile(); | ||
| 105 | |||
| 106 | // Clear mux bits (bits 8-11) and set new mux value | ||
| 107 | let new_val = (current_val & !(0xF << 8)) | ((mux as u32) << 8); | ||
| 108 | |||
| 109 | // Write back the new value | ||
| 110 | pcr_addr.write_volatile(new_val); | ||
| 111 | |||
| 112 | core::arch::asm!("dsb sy; isb sy"); | ||
| 113 | } | ||
| 114 | |||
| 115 | /// Configure a pin for GPIO mode (ALT0). | ||
| 116 | /// This is a convenience function that calls set_pin_mux with mux=0. | ||
| 117 | /// | ||
| 118 | /// # Arguments | ||
| 119 | /// * `port` - Port number (0-4) | ||
| 120 | /// * `pin` - Pin number (varies by port: PORT0=0-7, PORT1=0-19, PORT2=0-26, PORT3=0-31, PORT4=0-7) | ||
| 121 | pub unsafe fn set_pin_mux_gpio(port: u8, pin: u8) { | ||
| 122 | set_pin_mux(port, pin, 0); | ||
| 123 | } | ||
diff --git a/src/reset.rs b/src/reset.rs new file mode 100644 index 000000000..1c131d1cc --- /dev/null +++ b/src/reset.rs | |||
| @@ -0,0 +1,112 @@ | |||
| 1 | //! Reset control helpers built on PAC field writers. | ||
| 2 | use crate::pac; | ||
| 3 | |||
| 4 | /// Trait describing a reset line that can be asserted/deasserted. | ||
| 5 | pub trait ResetLine { | ||
| 6 | /// Drive the peripheral out of reset. | ||
| 7 | unsafe fn release(mrcc: &pac::mrcc0::RegisterBlock); | ||
| 8 | |||
| 9 | /// Drive the peripheral into reset. | ||
| 10 | unsafe fn assert(mrcc: &pac::mrcc0::RegisterBlock); | ||
| 11 | |||
| 12 | /// Check whether the peripheral is currently released. | ||
| 13 | fn is_released(mrcc: &pac::mrcc0::RegisterBlock) -> bool; | ||
| 14 | } | ||
| 15 | |||
| 16 | /// Release a reset line for the given peripheral set. | ||
| 17 | #[inline] | ||
| 18 | pub unsafe fn release<R: ResetLine>(peripherals: &pac::Peripherals) { | ||
| 19 | R::release(&peripherals.mrcc0); | ||
| 20 | } | ||
| 21 | |||
| 22 | /// Assert a reset line for the given peripheral set. | ||
| 23 | #[inline] | ||
| 24 | pub unsafe fn assert<R: ResetLine>(peripherals: &pac::Peripherals) { | ||
| 25 | R::assert(&peripherals.mrcc0); | ||
| 26 | } | ||
| 27 | |||
| 28 | /// Pulse a reset line (assert then release) with a short delay. | ||
| 29 | #[inline] | ||
| 30 | pub unsafe fn pulse<R: ResetLine>(peripherals: &pac::Peripherals) { | ||
| 31 | let mrcc = &peripherals.mrcc0; | ||
| 32 | R::assert(mrcc); | ||
| 33 | cortex_m::asm::nop(); | ||
| 34 | cortex_m::asm::nop(); | ||
| 35 | R::release(mrcc); | ||
| 36 | } | ||
| 37 | |||
| 38 | macro_rules! impl_reset_line { | ||
| 39 | ($name:ident, $reg:ident, $field:ident) => { | ||
| 40 | pub struct $name; | ||
| 41 | |||
| 42 | impl ResetLine for $name { | ||
| 43 | #[inline] | ||
| 44 | unsafe fn release(mrcc: &pac::mrcc0::RegisterBlock) { | ||
| 45 | mrcc.$reg().modify(|_, w| w.$field().enabled()); | ||
| 46 | } | ||
| 47 | |||
| 48 | #[inline] | ||
| 49 | unsafe fn assert(mrcc: &pac::mrcc0::RegisterBlock) { | ||
| 50 | mrcc.$reg().modify(|_, w| w.$field().disabled()); | ||
| 51 | } | ||
| 52 | |||
| 53 | #[inline] | ||
| 54 | fn is_released(mrcc: &pac::mrcc0::RegisterBlock) -> bool { | ||
| 55 | mrcc.$reg().read().$field().is_enabled() | ||
| 56 | } | ||
| 57 | } | ||
| 58 | }; | ||
| 59 | } | ||
| 60 | |||
| 61 | pub mod line { | ||
| 62 | use super::*; | ||
| 63 | |||
| 64 | impl_reset_line!(Port2, mrcc_glb_rst1, port2); | ||
| 65 | impl_reset_line!(Port3, mrcc_glb_rst1, port3); | ||
| 66 | impl_reset_line!(Gpio3, mrcc_glb_rst2, gpio3); | ||
| 67 | impl_reset_line!(Lpuart2, mrcc_glb_rst0, lpuart2); | ||
| 68 | impl_reset_line!(Ostimer0, mrcc_glb_rst1, ostimer0); | ||
| 69 | impl_reset_line!(Port1, mrcc_glb_rst1, port1); | ||
| 70 | impl_reset_line!(Adc1, mrcc_glb_rst1, adc1); | ||
| 71 | } | ||
| 72 | |||
| 73 | #[inline] | ||
| 74 | pub unsafe fn release_reset_port2(peripherals: &pac::Peripherals) { | ||
| 75 | release::<line::Port2>(peripherals); | ||
| 76 | } | ||
| 77 | |||
| 78 | #[inline] | ||
| 79 | pub unsafe fn release_reset_port3(peripherals: &pac::Peripherals) { | ||
| 80 | release::<line::Port3>(peripherals); | ||
| 81 | } | ||
| 82 | |||
| 83 | #[inline] | ||
| 84 | pub unsafe fn release_reset_gpio3(peripherals: &pac::Peripherals) { | ||
| 85 | release::<line::Gpio3>(peripherals); | ||
| 86 | } | ||
| 87 | |||
| 88 | #[inline] | ||
| 89 | pub unsafe fn release_reset_lpuart2(peripherals: &pac::Peripherals) { | ||
| 90 | release::<line::Lpuart2>(peripherals); | ||
| 91 | } | ||
| 92 | |||
| 93 | #[inline] | ||
| 94 | pub unsafe fn release_reset_ostimer0(peripherals: &pac::Peripherals) { | ||
| 95 | release::<line::Ostimer0>(peripherals); | ||
| 96 | } | ||
| 97 | |||
| 98 | /// Convenience shim retained for existing call sites. | ||
| 99 | #[inline] | ||
| 100 | pub unsafe fn reset_ostimer0(peripherals: &pac::Peripherals) { | ||
| 101 | pulse::<line::Ostimer0>(peripherals); | ||
| 102 | } | ||
| 103 | |||
| 104 | #[inline] | ||
| 105 | pub unsafe fn release_reset_port1(peripherals: &pac::Peripherals) { | ||
| 106 | release::<line::Port1>(peripherals); | ||
| 107 | } | ||
| 108 | |||
| 109 | #[inline] | ||
| 110 | pub unsafe fn release_reset_adc1(peripherals: &pac::Peripherals) { | ||
| 111 | release::<line::Adc1>(peripherals); | ||
| 112 | } | ||
diff --git a/src/rtc.rs b/src/rtc.rs new file mode 100644 index 000000000..d62da1f0a --- /dev/null +++ b/src/rtc.rs | |||
| @@ -0,0 +1,284 @@ | |||
| 1 | //! RTC DateTime driver. | ||
| 2 | use core::sync::atomic::{AtomicBool, Ordering}; | ||
| 3 | |||
| 4 | use crate::pac; | ||
| 5 | use crate::pac::rtc0::cr::Um; | ||
| 6 | |||
| 7 | type Regs = pac::rtc0::RegisterBlock; | ||
| 8 | |||
| 9 | static ALARM_TRIGGERED: AtomicBool = AtomicBool::new(false); | ||
| 10 | |||
| 11 | // Token-based instance pattern like embassy-imxrt | ||
| 12 | pub trait Instance { | ||
| 13 | fn ptr() -> *const Regs; | ||
| 14 | } | ||
| 15 | |||
| 16 | /// Token for RTC0 | ||
| 17 | pub type Rtc0 = crate::peripherals::RTC0; | ||
| 18 | impl Instance for crate::peripherals::RTC0 { | ||
| 19 | #[inline(always)] | ||
| 20 | fn ptr() -> *const Regs { | ||
| 21 | pac::Rtc0::ptr() | ||
| 22 | } | ||
| 23 | } | ||
| 24 | |||
| 25 | // Also implement Instance for the Peri wrapper type | ||
| 26 | impl Instance for embassy_hal_internal::Peri<'_, crate::peripherals::RTC0> { | ||
| 27 | #[inline(always)] | ||
| 28 | fn ptr() -> *const Regs { | ||
| 29 | pac::Rtc0::ptr() | ||
| 30 | } | ||
| 31 | } | ||
| 32 | |||
| 33 | const DAYS_IN_A_YEAR: u32 = 365; | ||
| 34 | const SECONDS_IN_A_DAY: u32 = 86400; | ||
| 35 | const SECONDS_IN_A_HOUR: u32 = 3600; | ||
| 36 | const SECONDS_IN_A_MINUTE: u32 = 60; | ||
| 37 | const YEAR_RANGE_START: u16 = 1970; | ||
| 38 | |||
| 39 | #[derive(Debug, Clone, Copy)] | ||
| 40 | pub struct RtcDateTime { | ||
| 41 | pub year: u16, | ||
| 42 | pub month: u8, | ||
| 43 | pub day: u8, | ||
| 44 | pub hour: u8, | ||
| 45 | pub minute: u8, | ||
| 46 | pub second: u8, | ||
| 47 | } | ||
| 48 | #[derive(Copy, Clone)] | ||
| 49 | pub struct RtcConfig { | ||
| 50 | #[allow(dead_code)] | ||
| 51 | wakeup_select: bool, | ||
| 52 | update_mode: Um, | ||
| 53 | #[allow(dead_code)] | ||
| 54 | supervisor_access: bool, | ||
| 55 | compensation_interval: u8, | ||
| 56 | compensation_time: u8, | ||
| 57 | } | ||
| 58 | |||
| 59 | #[derive(Copy, Clone)] | ||
| 60 | pub struct RtcInterruptEnable; | ||
| 61 | impl RtcInterruptEnable { | ||
| 62 | pub const RTC_TIME_INVALID_INTERRUPT_ENABLE: u32 = 1 << 0; | ||
| 63 | pub const RTC_TIME_OVERFLOW_INTERRUPT_ENABLE: u32 = 1 << 1; | ||
| 64 | pub const RTC_ALARM_INTERRUPT_ENABLE: u32 = 1 << 2; | ||
| 65 | pub const RTC_SECONDS_INTERRUPT_ENABLE: u32 = 1 << 4; | ||
| 66 | } | ||
| 67 | |||
| 68 | pub fn convert_datetime_to_seconds(datetime: &RtcDateTime) -> u32 { | ||
| 69 | let month_days: [u16; 13] = [0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]; | ||
| 70 | |||
| 71 | let mut seconds = (datetime.year as u32 - 1970) * DAYS_IN_A_YEAR; | ||
| 72 | seconds += (datetime.year as u32 / 4) - (1970 / 4); | ||
| 73 | seconds += month_days[datetime.month as usize] as u32; | ||
| 74 | seconds += datetime.day as u32 - 1; | ||
| 75 | |||
| 76 | if (datetime.year & 3 == 0) && (datetime.month <= 2) { | ||
| 77 | seconds -= 1; | ||
| 78 | } | ||
| 79 | |||
| 80 | seconds = seconds * SECONDS_IN_A_DAY | ||
| 81 | + (datetime.hour as u32 * SECONDS_IN_A_HOUR) | ||
| 82 | + (datetime.minute as u32 * SECONDS_IN_A_MINUTE) | ||
| 83 | + datetime.second as u32; | ||
| 84 | |||
| 85 | seconds | ||
| 86 | } | ||
| 87 | |||
| 88 | pub fn convert_seconds_to_datetime(seconds: u32) -> RtcDateTime { | ||
| 89 | let mut seconds_remaining = seconds; | ||
| 90 | let mut days = seconds_remaining / SECONDS_IN_A_DAY + 1; | ||
| 91 | seconds_remaining %= SECONDS_IN_A_DAY; | ||
| 92 | |||
| 93 | let hour = (seconds_remaining / SECONDS_IN_A_HOUR) as u8; | ||
| 94 | seconds_remaining %= SECONDS_IN_A_HOUR; | ||
| 95 | let minute = (seconds_remaining / SECONDS_IN_A_MINUTE) as u8; | ||
| 96 | let second = (seconds_remaining % SECONDS_IN_A_MINUTE) as u8; | ||
| 97 | |||
| 98 | let mut year = YEAR_RANGE_START; | ||
| 99 | let mut days_in_year = DAYS_IN_A_YEAR; | ||
| 100 | |||
| 101 | while days > days_in_year { | ||
| 102 | days -= days_in_year; | ||
| 103 | year += 1; | ||
| 104 | |||
| 105 | days_in_year = if year % 4 == 0 { | ||
| 106 | DAYS_IN_A_YEAR + 1 | ||
| 107 | } else { | ||
| 108 | DAYS_IN_A_YEAR | ||
| 109 | }; | ||
| 110 | } | ||
| 111 | |||
| 112 | let mut days_per_month = [0u8, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; | ||
| 113 | if year % 4 == 0 { | ||
| 114 | days_per_month[2] = 29; | ||
| 115 | } | ||
| 116 | |||
| 117 | let mut month = 1; | ||
| 118 | for m in 1..=12 { | ||
| 119 | if days <= days_per_month[m] as u32 { | ||
| 120 | month = m; | ||
| 121 | break; | ||
| 122 | } else { | ||
| 123 | days -= days_per_month[m] as u32; | ||
| 124 | } | ||
| 125 | } | ||
| 126 | |||
| 127 | let day = days as u8; | ||
| 128 | |||
| 129 | RtcDateTime { | ||
| 130 | year, | ||
| 131 | month: month as u8, | ||
| 132 | day, | ||
| 133 | hour, | ||
| 134 | minute, | ||
| 135 | second, | ||
| 136 | } | ||
| 137 | } | ||
| 138 | |||
| 139 | pub fn get_default_config() -> RtcConfig { | ||
| 140 | RtcConfig { | ||
| 141 | wakeup_select: false, | ||
| 142 | update_mode: Um::Um0, | ||
| 143 | supervisor_access: false, | ||
| 144 | compensation_interval: 0, | ||
| 145 | compensation_time: 0, | ||
| 146 | } | ||
| 147 | } | ||
| 148 | /// Minimal RTC handle for a specific instance I (store the zero-sized token like embassy) | ||
| 149 | pub struct Rtc<I: Instance> { | ||
| 150 | _inst: core::marker::PhantomData<I>, | ||
| 151 | } | ||
| 152 | |||
| 153 | impl<I: Instance> Rtc<I> { | ||
| 154 | /// initialize RTC | ||
| 155 | pub fn new(_inst: impl Instance, config: RtcConfig) -> Self { | ||
| 156 | let rtc = unsafe { &*I::ptr() }; | ||
| 157 | |||
| 158 | /* RTC reset */ | ||
| 159 | rtc.cr().modify(|_, w| w.swr().set_bit()); | ||
| 160 | rtc.cr().modify(|_, w| w.swr().clear_bit()); | ||
| 161 | rtc.tsr().write(|w| unsafe { w.bits(1) }); | ||
| 162 | |||
| 163 | rtc.cr().modify(|_, w| w.um().variant(config.update_mode)); | ||
| 164 | |||
| 165 | rtc.tcr().modify(|_, w| unsafe { | ||
| 166 | w.cir() | ||
| 167 | .bits(config.compensation_interval) | ||
| 168 | .tcr() | ||
| 169 | .bits(config.compensation_time) | ||
| 170 | }); | ||
| 171 | |||
| 172 | Self { | ||
| 173 | _inst: core::marker::PhantomData, | ||
| 174 | } | ||
| 175 | } | ||
| 176 | |||
| 177 | pub fn set_datetime(&self, datetime: RtcDateTime) { | ||
| 178 | let rtc = unsafe { &*I::ptr() }; | ||
| 179 | let seconds = convert_datetime_to_seconds(&datetime); | ||
| 180 | rtc.tsr().write(|w| unsafe { w.bits(seconds) }); | ||
| 181 | } | ||
| 182 | |||
| 183 | pub fn get_datetime(&self) -> RtcDateTime { | ||
| 184 | let rtc = unsafe { &*I::ptr() }; | ||
| 185 | let seconds = rtc.tsr().read().bits(); | ||
| 186 | convert_seconds_to_datetime(seconds) | ||
| 187 | } | ||
| 188 | |||
| 189 | pub fn set_alarm(&self, alarm: RtcDateTime) { | ||
| 190 | let rtc = unsafe { &*I::ptr() }; | ||
| 191 | let seconds = convert_datetime_to_seconds(&alarm); | ||
| 192 | |||
| 193 | rtc.tar().write(|w| unsafe { w.bits(0) }); | ||
| 194 | let mut timeout = 10000; | ||
| 195 | while rtc.tar().read().bits() != 0 && timeout > 0 { | ||
| 196 | timeout -= 1; | ||
| 197 | } | ||
| 198 | |||
| 199 | rtc.tar().write(|w| unsafe { w.bits(seconds) }); | ||
| 200 | |||
| 201 | let mut timeout = 10000; | ||
| 202 | while rtc.tar().read().bits() != seconds && timeout > 0 { | ||
| 203 | timeout -= 1; | ||
| 204 | } | ||
| 205 | } | ||
| 206 | |||
| 207 | pub fn get_alarm(&self) -> RtcDateTime { | ||
| 208 | let rtc = unsafe { &*I::ptr() }; | ||
| 209 | let alarm_seconds = rtc.tar().read().bits(); | ||
| 210 | convert_seconds_to_datetime(alarm_seconds) | ||
| 211 | } | ||
| 212 | |||
| 213 | pub fn start(&self) { | ||
| 214 | let rtc = unsafe { &*I::ptr() }; | ||
| 215 | rtc.sr().modify(|_, w| w.tce().set_bit()); | ||
| 216 | } | ||
| 217 | |||
| 218 | pub fn stop(&self) { | ||
| 219 | let rtc = unsafe { &*I::ptr() }; | ||
| 220 | rtc.sr().modify(|_, w| w.tce().clear_bit()); | ||
| 221 | } | ||
| 222 | |||
| 223 | pub fn set_interrupt(&self, mask: u32) { | ||
| 224 | let rtc = unsafe { &*I::ptr() }; | ||
| 225 | |||
| 226 | if (RtcInterruptEnable::RTC_TIME_INVALID_INTERRUPT_ENABLE & mask) != 0 { | ||
| 227 | rtc.ier().modify(|_, w| w.tiie().tiie_1()); | ||
| 228 | } | ||
| 229 | if (RtcInterruptEnable::RTC_TIME_OVERFLOW_INTERRUPT_ENABLE & mask) != 0 { | ||
| 230 | rtc.ier().modify(|_, w| w.toie().toie_1()); | ||
| 231 | } | ||
| 232 | if (RtcInterruptEnable::RTC_ALARM_INTERRUPT_ENABLE & mask) != 0 { | ||
| 233 | rtc.ier().modify(|_, w| w.taie().taie_1()); | ||
| 234 | } | ||
| 235 | if (RtcInterruptEnable::RTC_SECONDS_INTERRUPT_ENABLE & mask) != 0 { | ||
| 236 | rtc.ier().modify(|_, w| w.tsie().tsie_1()); | ||
| 237 | } | ||
| 238 | |||
| 239 | ALARM_TRIGGERED.store(false, Ordering::SeqCst); | ||
| 240 | } | ||
| 241 | |||
| 242 | pub fn disable_interrupt(&self, mask: u32) { | ||
| 243 | let rtc = unsafe { &*I::ptr() }; | ||
| 244 | |||
| 245 | if (RtcInterruptEnable::RTC_TIME_INVALID_INTERRUPT_ENABLE & mask) != 0 { | ||
| 246 | rtc.ier().modify(|_, w| w.tiie().tiie_0()); | ||
| 247 | } | ||
| 248 | if (RtcInterruptEnable::RTC_TIME_OVERFLOW_INTERRUPT_ENABLE & mask) != 0 { | ||
| 249 | rtc.ier().modify(|_, w| w.toie().toie_0()); | ||
| 250 | } | ||
| 251 | if (RtcInterruptEnable::RTC_ALARM_INTERRUPT_ENABLE & mask) != 0 { | ||
| 252 | rtc.ier().modify(|_, w| w.taie().taie_0()); | ||
| 253 | } | ||
| 254 | if (RtcInterruptEnable::RTC_SECONDS_INTERRUPT_ENABLE & mask) != 0 { | ||
| 255 | rtc.ier().modify(|_, w| w.tsie().tsie_0()); | ||
| 256 | } | ||
| 257 | } | ||
| 258 | |||
| 259 | pub fn clear_alarm_flag(&self) { | ||
| 260 | let rtc = unsafe { &*I::ptr() }; | ||
| 261 | rtc.ier().modify(|_, w| w.taie().clear_bit()); | ||
| 262 | } | ||
| 263 | |||
| 264 | pub fn is_alarm_triggered(&self) -> bool { | ||
| 265 | ALARM_TRIGGERED.load(Ordering::Relaxed) | ||
| 266 | } | ||
| 267 | } | ||
| 268 | |||
| 269 | pub fn on_interrupt() { | ||
| 270 | let rtc = unsafe { &*pac::Rtc0::ptr() }; | ||
| 271 | // Check if this is actually a time alarm interrupt | ||
| 272 | let sr = rtc.sr().read(); | ||
| 273 | if sr.taf().bit_is_set() { | ||
| 274 | rtc.ier().modify(|_, w| w.taie().clear_bit()); | ||
| 275 | ALARM_TRIGGERED.store(true, Ordering::SeqCst); | ||
| 276 | } | ||
| 277 | } | ||
| 278 | |||
| 279 | pub struct RtcHandler; | ||
| 280 | impl crate::interrupt::typelevel::Handler<crate::interrupt::typelevel::RTC> for RtcHandler { | ||
| 281 | unsafe fn on_interrupt() { | ||
| 282 | on_interrupt(); | ||
| 283 | } | ||
| 284 | } | ||
diff --git a/src/uart.rs b/src/uart.rs new file mode 100644 index 000000000..3209a318d --- /dev/null +++ b/src/uart.rs | |||
| @@ -0,0 +1,306 @@ | |||
| 1 | //! Minimal polling UART2 bring-up replicating MCUXpresso hello_world ordering. | ||
| 2 | //! WARNING: This is a narrow implementation only for debug console (115200 8N1). | ||
| 3 | |||
| 4 | use core::cell::RefCell; | ||
| 5 | |||
| 6 | use cortex_m::interrupt::Mutex; | ||
| 7 | use embassy_sync::signal::Signal; | ||
| 8 | |||
| 9 | use crate::pac; | ||
| 10 | |||
| 11 | // svd2rust defines the shared LPUART RegisterBlock under lpuart0; all instances reuse it. | ||
| 12 | type Regs = pac::lpuart0::RegisterBlock; | ||
| 13 | |||
| 14 | // Token-based instance pattern like embassy-imxrt | ||
| 15 | pub trait Instance { | ||
| 16 | fn ptr() -> *const Regs; | ||
| 17 | } | ||
| 18 | |||
| 19 | /// Token for LPUART2 provided by embassy-hal-internal peripherals macro. | ||
| 20 | pub type Lpuart2 = crate::peripherals::LPUART2; | ||
| 21 | impl Instance for crate::peripherals::LPUART2 { | ||
| 22 | #[inline(always)] | ||
| 23 | fn ptr() -> *const Regs { | ||
| 24 | pac::Lpuart2::ptr() | ||
| 25 | } | ||
| 26 | } | ||
| 27 | |||
| 28 | // Also implement Instance for the Peri wrapper type | ||
| 29 | impl Instance for embassy_hal_internal::Peri<'_, crate::peripherals::LPUART2> { | ||
| 30 | #[inline(always)] | ||
| 31 | fn ptr() -> *const Regs { | ||
| 32 | pac::Lpuart2::ptr() | ||
| 33 | } | ||
| 34 | } | ||
| 35 | |||
| 36 | /// UART configuration (explicit src_hz; no hardcoded frequencies) | ||
| 37 | #[derive(Copy, Clone)] | ||
| 38 | pub struct Config { | ||
| 39 | pub src_hz: u32, | ||
| 40 | pub baud: u32, | ||
| 41 | pub parity: Parity, | ||
| 42 | pub stop_bits: StopBits, | ||
| 43 | } | ||
| 44 | |||
| 45 | #[derive(Copy, Clone)] | ||
| 46 | pub enum Parity { | ||
| 47 | None, | ||
| 48 | Even, | ||
| 49 | Odd, | ||
| 50 | } | ||
| 51 | #[derive(Copy, Clone)] | ||
| 52 | pub enum StopBits { | ||
| 53 | One, | ||
| 54 | Two, | ||
| 55 | } | ||
| 56 | |||
| 57 | impl Config { | ||
| 58 | pub fn new(src_hz: u32) -> Self { | ||
| 59 | Self { | ||
| 60 | src_hz, | ||
| 61 | baud: 115_200, | ||
| 62 | parity: Parity::None, | ||
| 63 | stop_bits: StopBits::One, | ||
| 64 | } | ||
| 65 | } | ||
| 66 | } | ||
| 67 | |||
| 68 | /// Compute a valid (OSR, SBR) tuple for given source clock and baud. | ||
| 69 | /// Uses a functional fold approach to find the best OSR/SBR combination | ||
| 70 | /// with minimal baud rate error. | ||
| 71 | fn compute_osr_sbr(src_hz: u32, baud: u32) -> (u8, u16) { | ||
| 72 | let (best_osr, best_sbr, _best_err) = (8u32..=32).fold( | ||
| 73 | (16u8, 4u16, u32::MAX), // (best_osr, best_sbr, best_err) | ||
| 74 | |(best_osr, best_sbr, best_err), osr| { | ||
| 75 | let denom = baud.saturating_mul(osr); | ||
| 76 | if denom == 0 { | ||
| 77 | return (best_osr, best_sbr, best_err); | ||
| 78 | } | ||
| 79 | |||
| 80 | let sbr = (src_hz + denom / 2) / denom; // round | ||
| 81 | if sbr == 0 || sbr > 0x1FFF { | ||
| 82 | return (best_osr, best_sbr, best_err); | ||
| 83 | } | ||
| 84 | |||
| 85 | let actual = src_hz / (osr * sbr); | ||
| 86 | let err = actual.abs_diff(baud); | ||
| 87 | |||
| 88 | // Update best if this is better, or same error but higher OSR | ||
| 89 | if err < best_err || (err == best_err && osr as u8 > best_osr) { | ||
| 90 | (osr as u8, sbr as u16, err) | ||
| 91 | } else { | ||
| 92 | (best_osr, best_sbr, best_err) | ||
| 93 | } | ||
| 94 | }, | ||
| 95 | ); | ||
| 96 | (best_osr, best_sbr) | ||
| 97 | } | ||
| 98 | |||
| 99 | /// Minimal UART handle for a specific instance I (store the zero-sized token like embassy) | ||
| 100 | pub struct Uart<I: Instance> { | ||
| 101 | _inst: core::marker::PhantomData<I>, | ||
| 102 | } | ||
| 103 | |||
| 104 | impl<I: Instance> Uart<I> { | ||
| 105 | /// Create and initialize LPUART (reset + config). Clocks and pins must be prepared by the caller. | ||
| 106 | pub fn new(_inst: impl Instance, cfg: Config) -> Self { | ||
| 107 | let l = unsafe { &*I::ptr() }; | ||
| 108 | // 1) software reset pulse | ||
| 109 | l.global().write(|w| w.rst().reset()); | ||
| 110 | cortex_m::asm::delay(3); // Short delay for reset to take effect | ||
| 111 | l.global().write(|w| w.rst().no_effect()); | ||
| 112 | cortex_m::asm::delay(10); // Allow peripheral to stabilize after reset | ||
| 113 | // 2) BAUD | ||
| 114 | let (osr, sbr) = compute_osr_sbr(cfg.src_hz, cfg.baud); | ||
| 115 | l.baud().modify(|_, w| { | ||
| 116 | let w = match cfg.stop_bits { | ||
| 117 | StopBits::One => w.sbns().one(), | ||
| 118 | StopBits::Two => w.sbns().two(), | ||
| 119 | }; | ||
| 120 | // OSR field encodes (osr-1); use raw bits to avoid a long match on all variants | ||
| 121 | let raw_osr = osr.saturating_sub(1) as u8; | ||
| 122 | unsafe { w.osr().bits(raw_osr).sbr().bits(sbr) } | ||
| 123 | }); | ||
| 124 | // 3) CTRL baseline and parity | ||
| 125 | l.ctrl().write(|w| { | ||
| 126 | let w = w.ilt().from_stop().idlecfg().idle_2(); | ||
| 127 | let w = match cfg.parity { | ||
| 128 | Parity::None => w.pe().disabled(), | ||
| 129 | Parity::Even => w.pe().enabled().pt().even(), | ||
| 130 | Parity::Odd => w.pe().enabled().pt().odd(), | ||
| 131 | }; | ||
| 132 | w.re().enabled().te().enabled().rie().disabled() | ||
| 133 | }); | ||
| 134 | // 4) FIFOs and WATER: keep it simple for polling; disable FIFOs and set RX watermark to 0 | ||
| 135 | l.fifo().modify(|_, w| { | ||
| 136 | w.txfe() | ||
| 137 | .disabled() | ||
| 138 | .rxfe() | ||
| 139 | .disabled() | ||
| 140 | .txflush() | ||
| 141 | .txfifo_rst() | ||
| 142 | .rxflush() | ||
| 143 | .rxfifo_rst() | ||
| 144 | }); | ||
| 145 | l.water() | ||
| 146 | .modify(|_, w| unsafe { w.txwater().bits(0).rxwater().bits(0) }); | ||
| 147 | Self { | ||
| 148 | _inst: core::marker::PhantomData, | ||
| 149 | } | ||
| 150 | } | ||
| 151 | |||
| 152 | /// Enable RX interrupts. The caller must ensure an appropriate IRQ handler is installed. | ||
| 153 | pub unsafe fn enable_rx_interrupts(&self) { | ||
| 154 | let l = &*I::ptr(); | ||
| 155 | l.ctrl().modify(|_, w| w.rie().enabled()); | ||
| 156 | } | ||
| 157 | |||
| 158 | #[inline(never)] | ||
| 159 | pub fn write_byte(&self, b: u8) { | ||
| 160 | let l = unsafe { &*I::ptr() }; | ||
| 161 | // Timeout after ~10ms at 12MHz (assuming 115200 baud, should be plenty) | ||
| 162 | const DATA_OFFSET: usize = 0x1C; // DATA register offset inside LPUART block | ||
| 163 | let data_ptr = unsafe { (I::ptr() as *mut u8).add(DATA_OFFSET) }; | ||
| 164 | for _ in 0..120000 { | ||
| 165 | if l.water().read().txcount().bits() == 0 { | ||
| 166 | unsafe { core::ptr::write_volatile(data_ptr, b) }; | ||
| 167 | return; | ||
| 168 | } | ||
| 169 | } | ||
| 170 | // If timeout, skip the write to avoid hanging | ||
| 171 | } | ||
| 172 | |||
| 173 | #[inline(never)] | ||
| 174 | pub fn write_str_blocking(&self, s: &str) { | ||
| 175 | for &b in s.as_bytes() { | ||
| 176 | if b == b'\n' { | ||
| 177 | self.write_byte(b'\r'); | ||
| 178 | } | ||
| 179 | self.write_byte(b); | ||
| 180 | } | ||
| 181 | } | ||
| 182 | pub fn read_byte_blocking(&self) -> u8 { | ||
| 183 | let l = unsafe { &*I::ptr() }; | ||
| 184 | while !l.stat().read().rdrf().is_rxdata() {} | ||
| 185 | (l.data().read().bits() & 0xFF) as u8 | ||
| 186 | } | ||
| 187 | } | ||
| 188 | |||
| 189 | // Simple ring buffer for UART RX data | ||
| 190 | const RX_BUFFER_SIZE: usize = 256; | ||
| 191 | pub struct RingBuffer { | ||
| 192 | buffer: [u8; RX_BUFFER_SIZE], | ||
| 193 | read_idx: usize, | ||
| 194 | write_idx: usize, | ||
| 195 | count: usize, | ||
| 196 | } | ||
| 197 | |||
| 198 | impl RingBuffer { | ||
| 199 | pub const fn new() -> Self { | ||
| 200 | Self { | ||
| 201 | buffer: [0; RX_BUFFER_SIZE], | ||
| 202 | read_idx: 0, | ||
| 203 | write_idx: 0, | ||
| 204 | count: 0, | ||
| 205 | } | ||
| 206 | } | ||
| 207 | |||
| 208 | pub fn push(&mut self, data: u8) -> bool { | ||
| 209 | if self.count >= RX_BUFFER_SIZE { | ||
| 210 | return false; // Buffer full | ||
| 211 | } | ||
| 212 | self.buffer[self.write_idx] = data; | ||
| 213 | self.write_idx = (self.write_idx + 1) % RX_BUFFER_SIZE; | ||
| 214 | self.count += 1; | ||
| 215 | true | ||
| 216 | } | ||
| 217 | |||
| 218 | pub fn pop(&mut self) -> Option<u8> { | ||
| 219 | if self.count == 0 { | ||
| 220 | return None; | ||
| 221 | } | ||
| 222 | let data = self.buffer[self.read_idx]; | ||
| 223 | self.read_idx = (self.read_idx + 1) % RX_BUFFER_SIZE; | ||
| 224 | self.count -= 1; | ||
| 225 | Some(data) | ||
| 226 | } | ||
| 227 | |||
| 228 | pub fn is_empty(&self) -> bool { | ||
| 229 | self.count == 0 | ||
| 230 | } | ||
| 231 | |||
| 232 | pub fn len(&self) -> usize { | ||
| 233 | self.count | ||
| 234 | } | ||
| 235 | } | ||
| 236 | |||
| 237 | // Global RX buffer shared between interrupt handler and UART instance | ||
| 238 | static RX_BUFFER: Mutex<RefCell<RingBuffer>> = Mutex::new(RefCell::new(RingBuffer::new())); | ||
| 239 | static RX_SIGNAL: Signal<embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex, ()> = Signal::new(); | ||
| 240 | |||
| 241 | // Debug counter for interrupt handler calls | ||
| 242 | static mut INTERRUPT_COUNT: u32 = 0; | ||
| 243 | |||
| 244 | impl<I: Instance> Uart<I> { | ||
| 245 | /// Read a byte asynchronously using interrupts | ||
| 246 | pub async fn read_byte_async(&self) -> u8 { | ||
| 247 | loop { | ||
| 248 | // Check if we have data in the buffer | ||
| 249 | let byte = cortex_m::interrupt::free(|cs| { | ||
| 250 | let mut buffer = RX_BUFFER.borrow(cs).borrow_mut(); | ||
| 251 | buffer.pop() | ||
| 252 | }); | ||
| 253 | |||
| 254 | if let Some(byte) = byte { | ||
| 255 | return byte; | ||
| 256 | } | ||
| 257 | |||
| 258 | // Wait for the interrupt signal | ||
| 259 | RX_SIGNAL.wait().await; | ||
| 260 | } | ||
| 261 | } | ||
| 262 | |||
| 263 | /// Check if there's data available in the RX buffer | ||
| 264 | pub fn rx_data_available(&self) -> bool { | ||
| 265 | cortex_m::interrupt::free(|cs| { | ||
| 266 | let buffer = RX_BUFFER.borrow(cs).borrow(); | ||
| 267 | !buffer.is_empty() | ||
| 268 | }) | ||
| 269 | } | ||
| 270 | |||
| 271 | /// Try to read a byte from RX buffer (non-blocking) | ||
| 272 | pub fn try_read_byte(&self) -> Option<u8> { | ||
| 273 | cortex_m::interrupt::free(|cs| { | ||
| 274 | let mut buffer = RX_BUFFER.borrow(cs).borrow_mut(); | ||
| 275 | buffer.pop() | ||
| 276 | }) | ||
| 277 | } | ||
| 278 | } | ||
| 279 | |||
| 280 | /// Type-level handler for LPUART2 interrupts, compatible with bind_interrupts!. | ||
| 281 | pub struct UartInterruptHandler; | ||
| 282 | |||
| 283 | impl crate::interrupt::typelevel::Handler<crate::interrupt::typelevel::LPUART2> for UartInterruptHandler { | ||
| 284 | unsafe fn on_interrupt() { | ||
| 285 | INTERRUPT_COUNT += 1; | ||
| 286 | |||
| 287 | let lpuart = &*pac::Lpuart2::ptr(); | ||
| 288 | |||
| 289 | // Check if we have RX data | ||
| 290 | if lpuart.stat().read().rdrf().is_rxdata() { | ||
| 291 | // Read the data byte | ||
| 292 | let data = (lpuart.data().read().bits() & 0xFF) as u8; | ||
| 293 | |||
| 294 | // Store in ring buffer | ||
| 295 | cortex_m::interrupt::free(|cs| { | ||
| 296 | let mut buffer = RX_BUFFER.borrow(cs).borrow_mut(); | ||
| 297 | if buffer.push(data) { | ||
| 298 | // Data added successfully, signal waiting tasks | ||
| 299 | RX_SIGNAL.signal(()); | ||
| 300 | } | ||
| 301 | }); | ||
| 302 | } | ||
| 303 | // Always clear any error flags that might cause spurious interrupts | ||
| 304 | let _ = lpuart.stat().read(); | ||
| 305 | } | ||
| 306 | } | ||
diff --git a/supply-chain/README.md b/supply-chain/README.md index d43d50485..12f8777b0 100644 --- a/supply-chain/README.md +++ b/supply-chain/README.md | |||
| @@ -1,83 +1,149 @@ | |||
| 1 | # Working with cargo vet | 1 | # Working with cargo vet |
| 2 | 2 | ||
| 3 | ## Introduction | 3 | ## Introduction |
| 4 | 4 | ||
| 5 | `cargo vet` is a tool to help ensure that third-party Rust dependencies have been audited by a trusted entity. | 5 | `cargo vet` is a tool to help ensure that third-party Rust dependencies have been audited by a trusted entity. |
| 6 | It matches all dependencies against a set of audits conducted by the authors of the project or entities they trust. | 6 | It matches all dependencies against a set of audits conducted by the authors of the project or entities they trust. |
| 7 | To learn more, visit [mozilla/cargo-vet](https://github.com/mozilla/cargo-vet) | 7 | To learn more, visit [mozilla/cargo-vet](https://github.com/mozilla/cargo-vet) |
| 8 | 8 | ||
| 9 | --- | 9 | --- |
| 10 | 10 | ||
| 11 | ## Adding a new dependency | 11 | ## Adding a new dependency |
| 12 | 12 | ||
| 13 | When updating or adding a new dependency, we need to ensure it's audited before being merged into main. | 13 | When updating or adding a new dependency, we need to ensure it's audited before being merged into main. |
| 14 | For our repositories, we have designated experts who are responsible for vetting any new dependencies being added to their repository. | 14 | For our repositories, we have designated experts who are responsible for vetting any new dependencies being added to their repository. |
| 15 | _It is the shared responsibility of the developer creating the PR and the auditors to conduct a successful audit._ | 15 | _It is the shared responsibility of the developer creating the PR and the auditors to conduct a successful audit._ |
| 16 | Follow the process below to ensure compliance: | 16 | Follow the process below to ensure compliance: |
| 17 | 17 | ||
| 18 | ### For Developers | 18 | ### For Developers |
| 19 | 1. **Respond to `cargo vet` failures**: | 19 | 1. **Respond to `cargo vet` failures**: |
| 20 | - If your PR fails the `cargo vet` step, the cargo-vet workflow will add a comment to the PR with a template questionnaire | 20 | - If your PR fails the `cargo vet` step, the cargo-vet workflow will add a comment to the PR with a template questionnaire |
| 21 | - Copy the questionnaire, fill it out and paste it as a new comment on the PR. This greatly helps the auditors get some context of the changes requiring the new dependencies | 21 | - Copy the questionnaire, fill it out and paste it as a new comment on the PR. This greatly helps the auditors get some context of the changes requiring the new dependencies |
| 22 | 22 | ||
| 23 | 2. **Engage with auditors**: | 23 | 2. **Engage with auditors**: |
| 24 | - Respond to any questions that the auditors might have regarding the need of any new dependencies | 24 | - Respond to any questions that the auditors might have regarding the need of any new dependencies |
| 25 | 25 | ||
| 26 | 3. **Rebase and verify**: | 26 | 3. **Rebase and verify**: |
| 27 | - At their discretion, auditors will check in their audits into either [rust-crate-audits](https://github.com/OpenDevicePartnership/rust-crate-audits) or into the same repository | 27 | - At their discretion, auditors will check in their audits into either [rust-crate-audits](https://github.com/OpenDevicePartnership/rust-crate-audits) or into the same repository |
| 28 | - Once the new audits have been merged, rebase your branch on main and verify it passes `cargo vet` | 28 | - Once the new audits have been merged, rebase your branch on main and verify it passes `cargo vet` |
| 29 | ```bash | 29 | ```bash |
| 30 | git fetch upstream | 30 | git fetch upstream |
| 31 | git rebase upstream/main | 31 | git rebase upstream/main |
| 32 | cargo vet | 32 | cargo vet |
| 33 | ``` | 33 | ``` |
| 34 | 34 | ||
| 35 | 4. **Update PR**: | 35 | 4. **Update PR**: |
| 36 | - If the audits were checked into rust-crate-audits, they will show up in _imports.lock_ on running `cargo vet`. In this case add the updated _imports.lock_ to your PR | 36 | - If the audits were checked into rust-crate-audits, they will show up in _imports.lock_ on running `cargo vet`. In this case add the updated _imports.lock_ to your PR |
| 37 | - If the audits were checked into the same repository, they will be present in _audits.toml_ after rebase and you can simply force push to your PR after rebase | 37 | - If the audits were checked into the same repository, they will be present in _audits.toml_ after rebase and you can simply force push to your PR after rebase |
| 38 | ```bash | 38 | ```bash |
| 39 | git push -f | 39 | git push -f |
| 40 | ``` | 40 | ``` |
| 41 | 41 | ||
| 42 | 5. **Check PR status**: | 42 | 5. **Check PR status**: |
| 43 | - The existing PR comment from the previous failure will be updated with a success message once the check passes | 43 | - The existing PR comment from the previous failure will be updated with a success message once the check passes |
| 44 | 44 | ||
| 45 | ### For Auditors | 45 | ### For Auditors |
| 46 | 46 | ||
| 47 | 1. **Review the questionnaire**: | 47 | 1. **Review the questionnaire**: |
| 48 | - Check the filled questionnaire on the PR once the developer responds to the `cargo vet` failure | 48 | - Check the filled questionnaire on the PR once the developer responds to the `cargo vet` failure |
| 49 | - Respond to the developer comment in case more information is needed | 49 | - Respond to the developer comment in case more information is needed |
| 50 | 50 | ||
| 51 | 2. **Audit new dependencies**: | 51 | 2. **Audit new dependencies**: |
| 52 | - Inspect the `cargo vet` failures using your preferred method | 52 | - Inspect the `cargo vet` failures using your preferred method |
| 53 | - Use [gh pr checkout](https://cli.github.com/manual/gh_pr_checkout) to checkout the PR and run `cargo vet --locked` | 53 | - Use [gh pr checkout](https://cli.github.com/manual/gh_pr_checkout) to checkout the PR and run `cargo vet --locked` |
| 54 | - Use [Github Pull Requests for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=GitHub.vscode-pull-request-github) to checkout the PR and run `cargo vet --locked` | 54 | - Use [Github Pull Requests for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=GitHub.vscode-pull-request-github) to checkout the PR and run `cargo vet --locked` |
| 55 | - For more suggestions: [Checking out pull requests locally](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/checking-out-pull-requests-locally) | 55 | - For more suggestions: [Checking out pull requests locally](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/checking-out-pull-requests-locally) |
| 56 | 56 | ||
| 57 | 3. **Follow `cargo vet` recommendations**: | 57 | 3. **Follow `cargo vet` recommendations**: |
| 58 | - Follow the recommendations of the `cargo vet` command output, either `cargo vet diff` for version update or `cargo vet inspect` for new dependencies | 58 | - Follow the recommendations of the `cargo vet` command output, either `cargo vet diff` for version update or `cargo vet inspect` for new dependencies |
| 59 | 59 | ||
| 60 | 4. **Record audits**: | 60 | 4. **Record audits**: |
| 61 | - Use `cargo vet certify` to add new audits to _audits.toml_ | 61 | - Use `cargo vet certify` to add new audits to _audits.toml_ |
| 62 | - Verify all dependencies pass using `cargo vet` | 62 | - Verify all dependencies pass using `cargo vet` |
| 63 | 63 | ||
| 64 | 5. **Decide audit location**: | 64 | 5. **Decide audit location**: |
| 65 | - **Shared audits**: New audits should ideally be shared across ODP repositories to reduce the overhead of multiple audits for the same dependencies. To facilitate this, it's recommended to cut and paste the new audits and submit as a separate PR to the _audits.toml_ in [rust-crate-audits](https://github.com/OpenDevicePartnership/rust-crate-audits) | 65 | - **Shared audits**: New audits should ideally be shared across ODP repositories to reduce the overhead of multiple audits for the same dependencies. To facilitate this, it's recommended to cut and paste the new audits and submit as a separate PR to the _audits.toml_ in [rust-crate-audits](https://github.com/OpenDevicePartnership/rust-crate-audits) |
| 66 | - If due to business reasons, the audits are not to be shared across repositories, copy the updated _audits.toml_ to a new branch off main in the same repository and submit the PR to update the audits | 66 | - If due to business reasons, the audits are not to be shared across repositories, copy the updated _audits.toml_ to a new branch off main in the same repository and submit the PR to update the audits |
| 67 | 67 | ||
| 68 | 6. **Communicate successful audit**: | 68 | 6. **Communicate successful audit**: |
| 69 | - Communicate to the PR developer via a PR comment so they can update the PR and get `cargo vet` to pass | 69 | - Communicate to the PR developer via a PR comment so they can update the PR and get `cargo vet` to pass |
| 70 | 70 | ||
| 71 | --- | 71 | --- |
| 72 | 72 | ||
| 73 | ## Tips for using `cargo vet`: | 73 | ## Audit criteria |
| 74 | 74 | `cargo vet` comes pre-equipped with two built-in criteria but supports adding new criteria to suit our needs. | |
| 75 | - **Update _imports.lock_**: | 75 | As defined [here](https://mozilla.github.io/cargo-vet/built-in-criteria.html), the default criteria are: |
| 76 | - Import trusted third party audits to reduce the number of new audits to be performed. Running `cargo vet` without `--locked` fetches new imports and updates _imports.lock_ with any audits that are helpful for our project. | 76 | |
| 77 | 77 | - **safe-to-run** | |
| 78 | - **Add exemptions**: | 78 | This crate can be compiled, run, and tested on a local workstation or in |
| 79 | - If an audit cannot be performed for some dependency due to time sensitivity or business justified reasons, use `cargo vet add-exemption <PACKAGE> <VERSION>` to add the dependency to exemptions in _config.toml_ | 79 | controlled automation without surprising consequences, such as: |
| 80 | - To add all remaining audits to exemptions at once, use `cargo vet regenerate exemptions` | 80 | * Reading or writing data from sensitive or unrelated parts of the filesystem. |
| 81 | 81 | * Installing software or reconfiguring the device. | |
| 82 | - **Prune unnecessary entries**: | 82 | * Connecting to untrusted network endpoints. |
| 83 | * Misuse of system resources (e.g. cryptocurrency mining). | ||
| 84 | |||
| 85 | - **safe-to-deploy** | ||
| 86 | This crate will not introduce a serious security vulnerability to production | ||
| 87 | software exposed to untrusted input. | ||
| 88 | |||
| 89 | Auditors are not required to perform a full logic review of the entire crate. | ||
| 90 | Rather, they must review enough to fully reason about the behavior of all unsafe | ||
| 91 | blocks and usage of powerful imports. For any reasonable usage of the crate in | ||
| 92 | real-world software, an attacker must not be able to manipulate the runtime | ||
| 93 | behavior of these sections in an exploitable or surprising way. | ||
| 94 | |||
| 95 | Ideally, all unsafe code is fully sound, and ambient capabilities (e.g. | ||
| 96 | filesystem access) are hardened against manipulation and consistent with the | ||
| 97 | advertised behavior of the crate. However, some discretion is permitted. In such | ||
| 98 | cases, the nature of the discretion should be recorded in the `notes` field of | ||
| 99 | the audit record. | ||
| 100 | |||
| 101 | For crates which generate deployed code (e.g. build dependencies or procedural | ||
| 102 | macros), reasonable usage of the crate should output code which meets the above | ||
| 103 | criteria. | ||
| 104 | |||
| 105 | **Note: `safe-to-deploy` implies `safe-to-run`** | ||
| 106 | |||
| 107 | --- | ||
| 108 | |||
| 109 | ## Conducting an audit | ||
| 110 | |||
| 111 | When performing an audit for a new or updated dependency, auditors may consider the following criteria to ensure the safety, reliability, and suitability of the crate for use in our projects: | ||
| 112 | |||
| 113 | - **Security**: | ||
| 114 | - Review the crate for known vulnerabilities or security advisories. | ||
| 115 | - Check for unsafe code usage and ensure it is justified and well-documented. | ||
| 116 | - Evaluate the crate’s history of security issues and responsiveness to reported problems. | ||
| 117 | |||
| 118 | - **Maintenance and Activity**: | ||
| 119 | - Assess the frequency of updates and the responsiveness of maintainers to issues and pull requests. | ||
| 120 | - Prefer crates that are actively maintained and have a healthy contributor base. | ||
| 121 | |||
| 122 | - **License Compliance**: | ||
| 123 | - Verify that the crate’s license is compatible with our project’s licensing requirements. | ||
| 124 | |||
| 125 | - **Community Trust and Adoption**: | ||
| 126 | - Consider the crate’s adoption in the wider Rust ecosystem. | ||
| 127 | - Prefer crates that are widely used and trusted by the community. | ||
| 128 | |||
| 129 | - **Functionality and Suitability**: | ||
| 130 | - Confirm that the crate provides the required functionality without unnecessary features or bloat. | ||
| 131 | - Evaluate whether the crate’s API is stable and unlikely to introduce breaking changes unexpectedly. | ||
| 132 | |||
| 133 | - **Audit Trail**: | ||
| 134 | - Record the audit decision, including any concerns, mitigations, or recommendations for future updates. | ||
| 135 | - If exemptions are granted, document the justification and any follow-up actions required. | ||
| 136 | |||
| 137 | --- | ||
| 138 | |||
| 139 | ## Tips for using `cargo vet`: | ||
| 140 | |||
| 141 | - **Update _imports.lock_**: | ||
| 142 | - Import trusted third party audits to reduce the number of new audits to be performed. Running `cargo vet` without `--locked` fetches new imports and updates _imports.lock_ with any audits that are helpful for our project. | ||
| 143 | |||
| 144 | - **Add exemptions**: | ||
| 145 | - If an audit cannot be performed for some dependency due to time sensitivity or business justified reasons, use `cargo vet add-exemption <PACKAGE> <VERSION>` to add the dependency to exemptions in _config.toml_ | ||
| 146 | - To add all remaining audits to exemptions at once, use `cargo vet regenerate exemptions` | ||
| 147 | |||
| 148 | - **Prune unnecessary entries**: | ||
| 83 | - Remove unnecessary exemptions and imports using `cargo vet prune` \ No newline at end of file | 149 | - Remove unnecessary exemptions and imports using `cargo vet prune` \ No newline at end of file |
diff --git a/supply-chain/audits.toml b/supply-chain/audits.toml index 2772ccb21..60ebedf9b 100644 --- a/supply-chain/audits.toml +++ b/supply-chain/audits.toml | |||
| @@ -1,4 +1,128 @@ | |||
| 1 | 1 | ||
| 2 | # cargo-vet audits file | 2 | # cargo-vet audits file |
| 3 | 3 | ||
| 4 | [audits] | 4 | [[audits.autocfg]] |
| 5 | who = "Felipe Balbi <[email protected]>" | ||
| 6 | criteria = "safe-to-deploy" | ||
| 7 | version = "1.5.0" | ||
| 8 | |||
| 9 | [[audits.cfg-if]] | ||
| 10 | who = "Felipe Balbi <[email protected]>" | ||
| 11 | criteria = "safe-to-deploy" | ||
| 12 | version = "1.0.4" | ||
| 13 | |||
| 14 | [[audits.darling]] | ||
| 15 | who = "Felipe Balbi <[email protected]>" | ||
| 16 | criteria = "safe-to-deploy" | ||
| 17 | version = "0.20.11" | ||
| 18 | |||
| 19 | [[audits.darling_core]] | ||
| 20 | who = "Felipe Balbi <[email protected]>" | ||
| 21 | criteria = "safe-to-deploy" | ||
| 22 | version = "0.20.11" | ||
| 23 | |||
| 24 | [[audits.darling_macro]] | ||
| 25 | who = "Felipe Balbi <[email protected]>" | ||
| 26 | criteria = "safe-to-deploy" | ||
| 27 | version = "0.20.11" | ||
| 28 | |||
| 29 | [[audits.defmt-rtt]] | ||
| 30 | who = "Felipe Balbi <[email protected]>" | ||
| 31 | criteria = "safe-to-deploy" | ||
| 32 | version = "1.0.0" | ||
| 33 | notes = "defmt-rtt is used for all our logging purposes. Version 1.0.0 merely stabilizes what was already available previously." | ||
| 34 | |||
| 35 | [[audits.defmt-rtt]] | ||
| 36 | who = "Felipe Balbi <[email protected]>" | ||
| 37 | criteria = "safe-to-deploy" | ||
| 38 | delta = "1.0.0 -> 1.1.0" | ||
| 39 | |||
| 40 | [[audits.document-features]] | ||
| 41 | who = "Felipe Balbi <[email protected]>" | ||
| 42 | criteria = "safe-to-deploy" | ||
| 43 | version = "0.2.12" | ||
| 44 | |||
| 45 | [[audits.document-features]] | ||
| 46 | who = "Felipe Balbi <[email protected]>" | ||
| 47 | criteria = "safe-to-run" | ||
| 48 | version = "0.2.12" | ||
| 49 | |||
| 50 | [[audits.embassy-executor]] | ||
| 51 | who = "Felipe Balbi <[email protected]>" | ||
| 52 | criteria = "safe-to-deploy" | ||
| 53 | version = "0.9.1" | ||
| 54 | |||
| 55 | [[audits.embassy-executor-macros]] | ||
| 56 | who = "Felipe Balbi <[email protected]>" | ||
| 57 | criteria = "safe-to-deploy" | ||
| 58 | version = "0.7.0" | ||
| 59 | |||
| 60 | [[audits.embassy-executor-timer-queue]] | ||
| 61 | who = "Felipe Balbi <[email protected]>" | ||
| 62 | criteria = "safe-to-deploy" | ||
| 63 | version = "0.1.0" | ||
| 64 | |||
| 65 | [[audits.embassy-executor-timer-queue]] | ||
| 66 | who = "Felipe Balbi <[email protected]>" | ||
| 67 | criteria = "safe-to-deploy" | ||
| 68 | version = "0.1.0" | ||
| 69 | |||
| 70 | [[audits.embassy-time-queue-utils]] | ||
| 71 | who = "Felipe Balbi <[email protected]>" | ||
| 72 | criteria = "safe-to-deploy" | ||
| 73 | version = "0.3.0" | ||
| 74 | |||
| 75 | [[audits.ident_case]] | ||
| 76 | who = "Felipe Balbi <[email protected]>" | ||
| 77 | criteria = "safe-to-deploy" | ||
| 78 | version = "1.0.1" | ||
| 79 | |||
| 80 | [[audits.litrs]] | ||
| 81 | who = "Felipe Balbi <[email protected]>" | ||
| 82 | criteria = "safe-to-deploy" | ||
| 83 | version = "1.0.0" | ||
| 84 | |||
| 85 | [[audits.panic-probe]] | ||
| 86 | who = "Felipe Balbi <[email protected]>" | ||
| 87 | criteria = "safe-to-deploy" | ||
| 88 | version = "1.0.0" | ||
| 89 | |||
| 90 | [[audits.proc-macro2]] | ||
| 91 | who = "Felipe Balbi <[email protected]>" | ||
| 92 | criteria = "safe-to-deploy" | ||
| 93 | version = "1.0.103" | ||
| 94 | |||
| 95 | [[audits.quote]] | ||
| 96 | who = "Felipe Balbi <[email protected]>" | ||
| 97 | criteria = "safe-to-deploy" | ||
| 98 | version = "1.0.42" | ||
| 99 | |||
| 100 | [[audits.stable_deref_trait]] | ||
| 101 | who = "Felipe Balbi <[email protected]>" | ||
| 102 | criteria = "safe-to-deploy" | ||
| 103 | version = "1.2.1" | ||
| 104 | |||
| 105 | [[audits.static_cell]] | ||
| 106 | who = "jerrysxie <[email protected]>" | ||
| 107 | criteria = "safe-to-run" | ||
| 108 | delta = "2.1.0 -> 2.1.1" | ||
| 109 | |||
| 110 | [[audits.syn]] | ||
| 111 | who = "Felipe Balbi <[email protected]>" | ||
| 112 | criteria = "safe-to-deploy" | ||
| 113 | delta = "2.0.100 -> 2.0.109" | ||
| 114 | |||
| 115 | [[audits.thiserror]] | ||
| 116 | who = "Felipe Balbi <[email protected]>" | ||
| 117 | criteria = "safe-to-deploy" | ||
| 118 | version = "2.0.17" | ||
| 119 | |||
| 120 | [[audits.thiserror-impl]] | ||
| 121 | who = "Felipe Balbi <[email protected]>" | ||
| 122 | criteria = "safe-to-deploy" | ||
| 123 | version = "2.0.17" | ||
| 124 | |||
| 125 | [[audits.unicode-ident]] | ||
| 126 | who = "Felipe Balbi <[email protected]>" | ||
| 127 | criteria = "safe-to-deploy" | ||
| 128 | version = "1.0.22" | ||
diff --git a/supply-chain/config.toml b/supply-chain/config.toml index 55618c2ff..5927b0b61 100644 --- a/supply-chain/config.toml +++ b/supply-chain/config.toml | |||
| @@ -12,3 +12,187 @@ url = "https://raw.githubusercontent.com/google/rust-crate-audits/main/audits.to | |||
| 12 | 12 | ||
| 13 | [imports.mozilla] | 13 | [imports.mozilla] |
| 14 | url = "https://raw.githubusercontent.com/mozilla/supply-chain/main/audits.toml" | 14 | url = "https://raw.githubusercontent.com/mozilla/supply-chain/main/audits.toml" |
| 15 | |||
| 16 | [[exemptions.az]] | ||
| 17 | version = "1.2.1" | ||
| 18 | criteria = "safe-to-deploy" | ||
| 19 | |||
| 20 | [[exemptions.bare-metal]] | ||
| 21 | version = "0.2.5" | ||
| 22 | criteria = "safe-to-deploy" | ||
| 23 | |||
| 24 | [[exemptions.bitfield]] | ||
| 25 | version = "0.13.2" | ||
| 26 | criteria = "safe-to-deploy" | ||
| 27 | |||
| 28 | [[exemptions.bitfield]] | ||
| 29 | version = "0.15.0" | ||
| 30 | criteria = "safe-to-deploy" | ||
| 31 | |||
| 32 | [[exemptions.chrono]] | ||
| 33 | version = "0.4.40" | ||
| 34 | criteria = "safe-to-deploy" | ||
| 35 | |||
| 36 | [[exemptions.cortex-m]] | ||
| 37 | version = "0.7.7" | ||
| 38 | criteria = "safe-to-deploy" | ||
| 39 | |||
| 40 | [[exemptions.cortex-m-rt]] | ||
| 41 | version = "0.7.5" | ||
| 42 | criteria = "safe-to-deploy" | ||
| 43 | |||
| 44 | [[exemptions.cortex-m-rt-macros]] | ||
| 45 | version = "0.7.5" | ||
| 46 | criteria = "safe-to-deploy" | ||
| 47 | |||
| 48 | [[exemptions.critical-section]] | ||
| 49 | version = "1.2.0" | ||
| 50 | criteria = "safe-to-deploy" | ||
| 51 | |||
| 52 | [[exemptions.defmt]] | ||
| 53 | version = "1.0.1" | ||
| 54 | criteria = "safe-to-deploy" | ||
| 55 | |||
| 56 | [[exemptions.defmt-macros]] | ||
| 57 | version = "1.0.1" | ||
| 58 | criteria = "safe-to-deploy" | ||
| 59 | |||
| 60 | [[exemptions.defmt-parser]] | ||
| 61 | version = "1.0.0" | ||
| 62 | criteria = "safe-to-deploy" | ||
| 63 | |||
| 64 | [[exemptions.embassy-embedded-hal]] | ||
| 65 | version = "0.5.0" | ||
| 66 | criteria = "safe-to-deploy" | ||
| 67 | |||
| 68 | [[exemptions.embassy-futures]] | ||
| 69 | version = "0.1.2" | ||
| 70 | criteria = "safe-to-deploy" | ||
| 71 | |||
| 72 | [[exemptions.embassy-hal-internal]] | ||
| 73 | version = "0.3.0" | ||
| 74 | criteria = "safe-to-deploy" | ||
| 75 | |||
| 76 | [[exemptions.embassy-sync]] | ||
| 77 | version = "0.7.2" | ||
| 78 | criteria = "safe-to-deploy" | ||
| 79 | |||
| 80 | [[exemptions.embassy-time]] | ||
| 81 | version = "0.5.0" | ||
| 82 | criteria = "safe-to-deploy" | ||
| 83 | |||
| 84 | [[exemptions.embassy-time-driver]] | ||
| 85 | version = "0.2.1" | ||
| 86 | criteria = "safe-to-deploy" | ||
| 87 | |||
| 88 | [[exemptions.embedded-hal]] | ||
| 89 | version = "0.2.7" | ||
| 90 | criteria = "safe-to-deploy" | ||
| 91 | |||
| 92 | [[exemptions.embedded-hal]] | ||
| 93 | version = "1.0.0" | ||
| 94 | criteria = "safe-to-deploy" | ||
| 95 | |||
| 96 | [[exemptions.embedded-hal-async]] | ||
| 97 | version = "1.0.0" | ||
| 98 | criteria = "safe-to-deploy" | ||
| 99 | |||
| 100 | [[exemptions.embedded-hal-nb]] | ||
| 101 | version = "1.0.0" | ||
| 102 | criteria = "safe-to-deploy" | ||
| 103 | |||
| 104 | [[exemptions.embedded-io]] | ||
| 105 | version = "0.6.1" | ||
| 106 | criteria = "safe-to-deploy" | ||
| 107 | |||
| 108 | [[exemptions.embedded-io-async]] | ||
| 109 | version = "0.6.1" | ||
| 110 | criteria = "safe-to-deploy" | ||
| 111 | |||
| 112 | [[exemptions.embedded-storage]] | ||
| 113 | version = "0.3.1" | ||
| 114 | criteria = "safe-to-deploy" | ||
| 115 | |||
| 116 | [[exemptions.embedded-storage-async]] | ||
| 117 | version = "0.4.1" | ||
| 118 | criteria = "safe-to-deploy" | ||
| 119 | |||
| 120 | [[exemptions.fixed]] | ||
| 121 | version = "1.29.0" | ||
| 122 | criteria = "safe-to-deploy" | ||
| 123 | |||
| 124 | [[exemptions.futures-core]] | ||
| 125 | version = "0.3.31" | ||
| 126 | criteria = "safe-to-deploy" | ||
| 127 | |||
| 128 | [[exemptions.futures-sink]] | ||
| 129 | version = "0.3.31" | ||
| 130 | criteria = "safe-to-deploy" | ||
| 131 | |||
| 132 | [[exemptions.hash32]] | ||
| 133 | version = "0.3.1" | ||
| 134 | criteria = "safe-to-deploy" | ||
| 135 | |||
| 136 | [[exemptions.heapless]] | ||
| 137 | version = "0.8.0" | ||
| 138 | criteria = "safe-to-deploy" | ||
| 139 | |||
| 140 | [[exemptions.itertools]] | ||
| 141 | version = "0.11.0" | ||
| 142 | criteria = "safe-to-deploy" | ||
| 143 | |||
| 144 | [[exemptions.log]] | ||
| 145 | version = "0.4.27" | ||
| 146 | criteria = "safe-to-deploy" | ||
| 147 | |||
| 148 | [[exemptions.mimxrt600-fcb]] | ||
| 149 | version = "0.2.1" | ||
| 150 | criteria = "safe-to-deploy" | ||
| 151 | |||
| 152 | [[exemptions.paste]] | ||
| 153 | version = "1.0.15" | ||
| 154 | criteria = "safe-to-deploy" | ||
| 155 | |||
| 156 | [[exemptions.portable-atomic]] | ||
| 157 | version = "1.11.0" | ||
| 158 | criteria = "safe-to-run" | ||
| 159 | |||
| 160 | [[exemptions.proc-macro-error-attr2]] | ||
| 161 | version = "2.0.0" | ||
| 162 | criteria = "safe-to-deploy" | ||
| 163 | |||
| 164 | [[exemptions.proc-macro-error2]] | ||
| 165 | version = "2.0.1" | ||
| 166 | criteria = "safe-to-deploy" | ||
| 167 | |||
| 168 | [[exemptions.rustc_version]] | ||
| 169 | version = "0.2.3" | ||
| 170 | criteria = "safe-to-deploy" | ||
| 171 | |||
| 172 | [[exemptions.semver]] | ||
| 173 | version = "0.9.0" | ||
| 174 | criteria = "safe-to-deploy" | ||
| 175 | |||
| 176 | [[exemptions.semver-parser]] | ||
| 177 | version = "0.7.0" | ||
| 178 | criteria = "safe-to-deploy" | ||
| 179 | |||
| 180 | [[exemptions.static_cell]] | ||
| 181 | version = "2.1.0" | ||
| 182 | criteria = "safe-to-run" | ||
| 183 | |||
| 184 | [[exemptions.syn]] | ||
| 185 | version = "2.0.100" | ||
| 186 | criteria = "safe-to-deploy" | ||
| 187 | |||
| 188 | [[exemptions.typenum]] | ||
| 189 | version = "1.18.0" | ||
| 190 | criteria = "safe-to-deploy" | ||
| 191 | |||
| 192 | [[exemptions.vcell]] | ||
| 193 | version = "0.1.3" | ||
| 194 | criteria = "safe-to-deploy" | ||
| 195 | |||
| 196 | [[exemptions.volatile-register]] | ||
| 197 | version = "0.2.2" | ||
| 198 | criteria = "safe-to-deploy" | ||
diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock index 219dba4ee..3f541e59f 100644 --- a/supply-chain/imports.lock +++ b/supply-chain/imports.lock | |||
| @@ -3,6 +3,470 @@ | |||
| 3 | 3 | ||
| 4 | [audits.OpenDevicePartnership.audits] | 4 | [audits.OpenDevicePartnership.audits] |
| 5 | 5 | ||
| 6 | [audits.google.audits] | 6 | [[audits.google.audits.autocfg]] |
| 7 | who = "Manish Goregaokar <[email protected]>" | ||
| 8 | criteria = "safe-to-deploy" | ||
| 9 | version = "1.4.0" | ||
| 10 | notes = "Contains no unsafe" | ||
| 11 | aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" | ||
| 7 | 12 | ||
| 8 | [audits.mozilla.audits] | 13 | [[audits.google.audits.bitflags]] |
| 14 | who = "Lukasz Anforowicz <[email protected]>" | ||
| 15 | criteria = "safe-to-deploy" | ||
| 16 | version = "1.3.2" | ||
| 17 | notes = """ | ||
| 18 | Security review of earlier versions of the crate can be found at | ||
| 19 | (Google-internal, sorry): go/image-crate-chromium-security-review | ||
| 20 | |||
| 21 | The crate exposes a function marked as `unsafe`, but doesn't use any | ||
| 22 | `unsafe` blocks (except for tests of the single `unsafe` function). I | ||
| 23 | think this justifies marking this crate as `ub-risk-1`. | ||
| 24 | |||
| 25 | Additional review comments can be found at https://crrev.com/c/4723145/31 | ||
| 26 | """ | ||
| 27 | aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" | ||
| 28 | |||
| 29 | [[audits.google.audits.bytemuck]] | ||
| 30 | who = "Lukasz Anforowicz <[email protected]>" | ||
| 31 | criteria = "safe-to-deploy" | ||
| 32 | version = "1.16.3" | ||
| 33 | notes = """ | ||
| 34 | Review notes from the original audit (of 1.14.3) may be found in | ||
| 35 | https://crrev.com/c/5362675. Note that this audit has initially missed UB risk | ||
| 36 | that was fixed in 1.16.2 - see https://github.com/Lokathor/bytemuck/pull/258. | ||
| 37 | Because of this, the original audit has been edited to certify version `1.16.3` | ||
| 38 | instead (see also https://crrev.com/c/5771867). | ||
| 39 | """ | ||
| 40 | aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" | ||
| 41 | |||
| 42 | [[audits.google.audits.bytemuck]] | ||
| 43 | who = "Lukasz Anforowicz <[email protected]>" | ||
| 44 | criteria = "safe-to-deploy" | ||
| 45 | delta = "1.16.3 -> 1.17.1" | ||
| 46 | notes = "Unsafe review comments can be found in https://crrev.com/c/5813463" | ||
| 47 | aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" | ||
| 48 | |||
| 49 | [[audits.google.audits.bytemuck]] | ||
| 50 | who = "Adrian Taylor <[email protected]>" | ||
| 51 | criteria = "safe-to-deploy" | ||
| 52 | delta = "1.17.1 -> 1.18.0" | ||
| 53 | notes = "No code changes - just altering feature flag arrangements" | ||
| 54 | aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" | ||
| 55 | |||
| 56 | [[audits.google.audits.bytemuck]] | ||
| 57 | who = "Adrian Taylor <[email protected]>" | ||
| 58 | criteria = "safe-to-deploy" | ||
| 59 | delta = "1.18.0 -> 1.19.0" | ||
| 60 | notes = "No code changes - just comment changes and adding the track_caller attribute." | ||
| 61 | aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" | ||
| 62 | |||
| 63 | [[audits.google.audits.bytemuck]] | ||
| 64 | who = "Lukasz Anforowicz <[email protected]>" | ||
| 65 | criteria = "safe-to-deploy" | ||
| 66 | delta = "1.19.0 -> 1.20.0" | ||
| 67 | notes = "`unsafe` review can be found at https://crrev.com/c/6096767" | ||
| 68 | aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" | ||
| 69 | |||
| 70 | [[audits.google.audits.bytemuck]] | ||
| 71 | who = "Adrian Taylor <[email protected]>" | ||
| 72 | criteria = "safe-to-deploy" | ||
| 73 | delta = "1.20.0 -> 1.21.0" | ||
| 74 | notes = "Unsafe review at https://chromium-review.googlesource.com/c/chromium/src/+/6111154/" | ||
| 75 | aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" | ||
| 76 | |||
| 77 | [[audits.google.audits.bytemuck]] | ||
| 78 | who = "Daniel Cheng <[email protected]>" | ||
| 79 | criteria = "safe-to-deploy" | ||
| 80 | delta = "1.21.0 -> 1.22.0" | ||
| 81 | notes = """ | ||
| 82 | This adds new instances of unsafe, but the uses are justified: | ||
| 83 | - BoxBytes is essentially a Box<[u8], which is Send + Sync, so also marking BoxBytes as Send + Sync is justified. | ||
| 84 | - core::num::Saturating<T> meets the criteria for Zeroable + Pod, so marking it as such is justified. | ||
| 85 | |||
| 86 | See https://crrev.com/c/6321863 for more audit notes. | ||
| 87 | """ | ||
| 88 | aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" | ||
| 89 | |||
| 90 | [[audits.google.audits.byteorder]] | ||
| 91 | who = "danakj <[email protected]>" | ||
| 92 | criteria = "safe-to-deploy" | ||
| 93 | version = "1.5.0" | ||
| 94 | notes = "Unsafe review in https://crrev.com/c/5838022" | ||
| 95 | aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" | ||
| 96 | |||
| 97 | [[audits.google.audits.cfg-if]] | ||
| 98 | who = "George Burgess IV <[email protected]>" | ||
| 99 | criteria = "safe-to-deploy" | ||
| 100 | version = "1.0.0" | ||
| 101 | aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" | ||
| 102 | |||
| 103 | [[audits.google.audits.either]] | ||
| 104 | who = "Manish Goregaokar <[email protected]>" | ||
| 105 | criteria = "safe-to-deploy" | ||
| 106 | version = "1.13.0" | ||
| 107 | notes = "Unsafe code pertaining to wrapping Pin APIs. Mostly passes invariants down." | ||
| 108 | aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" | ||
| 109 | |||
| 110 | [[audits.google.audits.either]] | ||
| 111 | who = "Daniel Cheng <[email protected]>" | ||
| 112 | criteria = "safe-to-deploy" | ||
| 113 | delta = "1.13.0 -> 1.14.0" | ||
| 114 | notes = """ | ||
| 115 | Inheriting ub-risk-1 from the baseline review of 1.13.0. While the delta has some diffs in unsafe code, they are either: | ||
| 116 | - migrating code to use helper macros | ||
| 117 | - migrating match patterns to take advantage of default bindings mode from RFC 2005 | ||
| 118 | Either way, the result is code that does exactly the same thing and does not change the risk of UB. | ||
| 119 | |||
| 120 | See https://crrev.com/c/6323164 for more audit details. | ||
| 121 | """ | ||
| 122 | aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" | ||
| 123 | |||
| 124 | [[audits.google.audits.either]] | ||
| 125 | who = "Lukasz Anforowicz <[email protected]>" | ||
| 126 | criteria = "safe-to-deploy" | ||
| 127 | delta = "1.14.0 -> 1.15.0" | ||
| 128 | notes = "The delta in `lib.rs` only tweaks doc comments and `#[cfg(feature = \"std\")]`." | ||
| 129 | aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" | ||
| 130 | |||
| 131 | [[audits.google.audits.nb]] | ||
| 132 | who = "George Burgess IV <[email protected]>" | ||
| 133 | criteria = "safe-to-deploy" | ||
| 134 | version = "1.0.0" | ||
| 135 | aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" | ||
| 136 | |||
| 137 | [[audits.google.audits.nb]] | ||
| 138 | who = "George Burgess IV <[email protected]>" | ||
| 139 | criteria = "safe-to-deploy" | ||
| 140 | delta = "1.0.0 -> 0.1.3" | ||
| 141 | aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" | ||
| 142 | |||
| 143 | [[audits.google.audits.nb]] | ||
| 144 | who = "George Burgess IV <[email protected]>" | ||
| 145 | criteria = "safe-to-deploy" | ||
| 146 | delta = "1.0.0 -> 1.1.0" | ||
| 147 | aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" | ||
| 148 | |||
| 149 | [[audits.google.audits.num-traits]] | ||
| 150 | who = "Manish Goregaokar <[email protected]>" | ||
| 151 | criteria = "safe-to-deploy" | ||
| 152 | version = "0.2.19" | ||
| 153 | notes = "Contains a single line of float-to-int unsafe with decent safety comments" | ||
| 154 | aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" | ||
| 155 | |||
| 156 | [[audits.google.audits.proc-macro2]] | ||
| 157 | who = "Lukasz Anforowicz <[email protected]>" | ||
| 158 | criteria = "safe-to-deploy" | ||
| 159 | version = "1.0.78" | ||
| 160 | notes = """ | ||
| 161 | Grepped for \"crypt\", \"cipher\", \"fs\", \"net\" - there were no hits | ||
| 162 | (except for a benign \"fs\" hit in a doc comment) | ||
| 163 | |||
| 164 | Notes from the `unsafe` review can be found in https://crrev.com/c/5385745. | ||
| 165 | """ | ||
| 166 | aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" | ||
| 167 | |||
| 168 | [[audits.google.audits.proc-macro2]] | ||
| 169 | who = "Adrian Taylor <[email protected]>" | ||
| 170 | criteria = "safe-to-deploy" | ||
| 171 | delta = "1.0.78 -> 1.0.79" | ||
| 172 | aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" | ||
| 173 | |||
| 174 | [[audits.google.audits.proc-macro2]] | ||
| 175 | who = "Adrian Taylor <[email protected]>" | ||
| 176 | criteria = "safe-to-deploy" | ||
| 177 | delta = "1.0.79 -> 1.0.80" | ||
| 178 | aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" | ||
| 179 | |||
| 180 | [[audits.google.audits.proc-macro2]] | ||
| 181 | who = "Dustin J. Mitchell <[email protected]>" | ||
| 182 | criteria = "safe-to-deploy" | ||
| 183 | delta = "1.0.80 -> 1.0.81" | ||
| 184 | notes = "Comment changes only" | ||
| 185 | aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" | ||
| 186 | |||
| 187 | [[audits.google.audits.proc-macro2]] | ||
| 188 | who = "danakj <[email protected]>" | ||
| 189 | criteria = "safe-to-deploy" | ||
| 190 | delta = "1.0.81 -> 1.0.82" | ||
| 191 | aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" | ||
| 192 | |||
| 193 | [[audits.google.audits.proc-macro2]] | ||
| 194 | who = "Dustin J. Mitchell <[email protected]>" | ||
| 195 | criteria = "safe-to-deploy" | ||
| 196 | delta = "1.0.82 -> 1.0.83" | ||
| 197 | notes = "Substantive change is replacing String with Box<str>, saving memory." | ||
| 198 | aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" | ||
| 199 | |||
| 200 | [[audits.google.audits.proc-macro2]] | ||
| 201 | who = "Lukasz Anforowicz <[email protected]>" | ||
| 202 | criteria = "safe-to-deploy" | ||
| 203 | delta = "1.0.83 -> 1.0.84" | ||
| 204 | notes = "Only doc comment changes in `src/lib.rs`." | ||
| 205 | aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" | ||
| 206 | |||
| 207 | [[audits.google.audits.proc-macro2]] | ||
| 208 | who = "[email protected]" | ||
| 209 | criteria = "safe-to-deploy" | ||
| 210 | delta = "1.0.84 -> 1.0.85" | ||
| 211 | notes = "Test-only changes." | ||
| 212 | aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" | ||
| 213 | |||
| 214 | [[audits.google.audits.proc-macro2]] | ||
| 215 | who = "Lukasz Anforowicz <[email protected]>" | ||
| 216 | criteria = "safe-to-deploy" | ||
| 217 | delta = "1.0.85 -> 1.0.86" | ||
| 218 | notes = """ | ||
| 219 | Comment-only changes in `build.rs`. | ||
| 220 | Reordering of `Cargo.toml` entries. | ||
| 221 | Just bumping up the version number in `lib.rs`. | ||
| 222 | Config-related changes in `test_size.rs`. | ||
| 223 | """ | ||
| 224 | aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" | ||
| 225 | |||
| 226 | [[audits.google.audits.proc-macro2]] | ||
| 227 | who = "danakj <[email protected]>" | ||
| 228 | criteria = "safe-to-deploy" | ||
| 229 | delta = "1.0.86 -> 1.0.87" | ||
| 230 | notes = "No new unsafe interactions." | ||
| 231 | aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" | ||
| 232 | |||
| 233 | [[audits.google.audits.proc-macro2]] | ||
| 234 | who = "Liza Burakova <[email protected]" | ||
| 235 | criteria = "safe-to-deploy" | ||
| 236 | delta = "1.0.87 -> 1.0.89" | ||
| 237 | notes = """ | ||
| 238 | Biggest change is adding error handling in build.rs. | ||
| 239 | Some config related changes in wrapper.rs. | ||
| 240 | """ | ||
| 241 | aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" | ||
| 242 | |||
| 243 | [[audits.google.audits.proc-macro2]] | ||
| 244 | who = "Lukasz Anforowicz <[email protected]>" | ||
| 245 | criteria = "safe-to-deploy" | ||
| 246 | delta = "1.0.89 -> 1.0.92" | ||
| 247 | notes = """ | ||
| 248 | I looked at the delta and the previous discussion at | ||
| 249 | https://chromium-review.googlesource.com/c/chromium/src/+/5385745/3#message-a8e2813129fa3779dab15acede408ee26d67b7f3 | ||
| 250 | and the changes look okay to me (including the `unsafe fn from_str_unchecked` | ||
| 251 | changes in `wrapper.rs`). | ||
| 252 | """ | ||
| 253 | aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" | ||
| 254 | |||
| 255 | [[audits.google.audits.proc-macro2]] | ||
| 256 | who = "Lukasz Anforowicz <[email protected]>" | ||
| 257 | criteria = "safe-to-deploy" | ||
| 258 | delta = "1.0.92 -> 1.0.93" | ||
| 259 | notes = "No `unsafe`-related changes." | ||
| 260 | aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" | ||
| 261 | |||
| 262 | [[audits.google.audits.proc-macro2]] | ||
| 263 | who = "Daniel Cheng <[email protected]>" | ||
| 264 | criteria = "safe-to-deploy" | ||
| 265 | delta = "1.0.93 -> 1.0.94" | ||
| 266 | notes = "Minor doc changes and clippy lint adjustments+fixes." | ||
| 267 | aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" | ||
| 268 | |||
| 269 | [[audits.google.audits.quote]] | ||
| 270 | who = "Lukasz Anforowicz <[email protected]>" | ||
| 271 | criteria = "safe-to-deploy" | ||
| 272 | version = "1.0.35" | ||
| 273 | notes = """ | ||
| 274 | Grepped for \"unsafe\", \"crypt\", \"cipher\", \"fs\", \"net\" - there were no hits | ||
| 275 | (except for benign \"net\" hit in tests and \"fs\" hit in README.md) | ||
| 276 | """ | ||
| 277 | aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" | ||
| 278 | |||
| 279 | [[audits.google.audits.quote]] | ||
| 280 | who = "Adrian Taylor <[email protected]>" | ||
| 281 | criteria = "safe-to-deploy" | ||
| 282 | delta = "1.0.35 -> 1.0.36" | ||
| 283 | aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" | ||
| 284 | |||
| 285 | [[audits.google.audits.quote]] | ||
| 286 | who = "Lukasz Anforowicz <[email protected]>" | ||
| 287 | criteria = "safe-to-deploy" | ||
| 288 | delta = "1.0.36 -> 1.0.37" | ||
| 289 | notes = """ | ||
| 290 | The delta just 1) inlines/expands `impl ToTokens` that used to be handled via | ||
| 291 | `primitive!` macro and 2) adds `impl ToTokens` for `CStr` and `CString`. | ||
| 292 | """ | ||
| 293 | aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" | ||
| 294 | |||
| 295 | [[audits.google.audits.quote]] | ||
| 296 | who = "Dustin J. Mitchell <[email protected]>" | ||
| 297 | criteria = "safe-to-deploy" | ||
| 298 | delta = "1.0.37 -> 1.0.38" | ||
| 299 | notes = "Still no unsafe" | ||
| 300 | aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" | ||
| 301 | |||
| 302 | [[audits.google.audits.quote]] | ||
| 303 | who = "Daniel Cheng <[email protected]>" | ||
| 304 | criteria = "safe-to-deploy" | ||
| 305 | delta = "1.0.38 -> 1.0.39" | ||
| 306 | notes = "Only minor changes for clippy lints and documentation." | ||
| 307 | aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" | ||
| 308 | |||
| 309 | [[audits.google.audits.quote]] | ||
| 310 | who = "Lukasz Anforowicz <[email protected]>" | ||
| 311 | criteria = "safe-to-deploy" | ||
| 312 | delta = "1.0.39 -> 1.0.40" | ||
| 313 | notes = """ | ||
| 314 | The delta is just a simplification of how `tokens.extend(...)` call is made. | ||
| 315 | Still no `unsafe` anywhere. | ||
| 316 | """ | ||
| 317 | aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" | ||
| 318 | |||
| 319 | [[audits.google.audits.rand_core]] | ||
| 320 | who = "Lukasz Anforowicz <[email protected]>" | ||
| 321 | criteria = "safe-to-deploy" | ||
| 322 | version = "0.6.4" | ||
| 323 | notes = """ | ||
| 324 | For more detailed unsafe review notes please see https://crrev.com/c/6362797 | ||
| 325 | """ | ||
| 326 | aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" | ||
| 327 | |||
| 328 | [[audits.google.audits.stable_deref_trait]] | ||
| 329 | who = "Manish Goregaokar <[email protected]>" | ||
| 330 | criteria = "safe-to-deploy" | ||
| 331 | version = "1.2.0" | ||
| 332 | notes = "Purely a trait, crates using this should be carefully vetted since self-referential stuff can be super tricky around various unsafe rust edges." | ||
| 333 | aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" | ||
| 334 | |||
| 335 | [[audits.google.audits.strsim]] | ||
| 336 | who = "[email protected]" | ||
| 337 | criteria = "safe-to-deploy" | ||
| 338 | version = "0.10.0" | ||
| 339 | notes = """ | ||
| 340 | Reviewed in https://crrev.com/c/5171063 | ||
| 341 | |||
| 342 | Previously reviewed during security review and the audit is grandparented in. | ||
| 343 | """ | ||
| 344 | aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" | ||
| 345 | |||
| 346 | [[audits.google.audits.unicode-ident]] | ||
| 347 | who = "Lukasz Anforowicz <[email protected]>" | ||
| 348 | criteria = "safe-to-deploy" | ||
| 349 | version = "1.0.12" | ||
| 350 | notes = ''' | ||
| 351 | I grepped for \"crypt\", \"cipher\", \"fs\", \"net\" - there were no hits. | ||
| 352 | |||
| 353 | All two functions from the public API of this crate use `unsafe` to avoid bound | ||
| 354 | checks for an array access. Cross-module analysis shows that the offsets can | ||
| 355 | be statically proven to be within array bounds. More details can be found in | ||
| 356 | the unsafe review CL at https://crrev.com/c/5350386. | ||
| 357 | |||
| 358 | This crate has been added to Chromium in https://crrev.com/c/3891618. | ||
| 359 | ''' | ||
| 360 | aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" | ||
| 361 | |||
| 362 | [[audits.google.audits.unicode-ident]] | ||
| 363 | who = "Dustin J. Mitchell <[email protected]>" | ||
| 364 | criteria = "safe-to-deploy" | ||
| 365 | delta = "1.0.12 -> 1.0.13" | ||
| 366 | notes = "Lots of table updates, and tables are assumed correct with unsafe `.get_unchecked()`, so ub-risk-2 is appropriate" | ||
| 367 | aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" | ||
| 368 | |||
| 369 | [[audits.google.audits.unicode-ident]] | ||
| 370 | who = "Lukasz Anforowicz <[email protected]>" | ||
| 371 | criteria = "safe-to-deploy" | ||
| 372 | delta = "1.0.13 -> 1.0.14" | ||
| 373 | notes = "Minimal delta in `.rs` files: new test assertions + doc changes." | ||
| 374 | aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" | ||
| 375 | |||
| 376 | [[audits.google.audits.unicode-ident]] | ||
| 377 | who = "Adrian Taylor <[email protected]>" | ||
| 378 | criteria = "safe-to-deploy" | ||
| 379 | delta = "1.0.14 -> 1.0.15" | ||
| 380 | notes = "No changes relevant to any of these criteria." | ||
| 381 | aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" | ||
| 382 | |||
| 383 | [[audits.google.audits.unicode-ident]] | ||
| 384 | who = "Liza Burakova <[email protected]>" | ||
| 385 | criteria = "safe-to-deploy" | ||
| 386 | delta = "1.0.15 -> 1.0.16" | ||
| 387 | aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" | ||
| 388 | |||
| 389 | [[audits.google.audits.unicode-ident]] | ||
| 390 | who = "Daniel Cheng <[email protected]>" | ||
| 391 | criteria = "safe-to-deploy" | ||
| 392 | delta = "1.0.16 -> 1.0.18" | ||
| 393 | notes = "Only minor comment and documentation updates." | ||
| 394 | aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" | ||
| 395 | |||
| 396 | [[audits.google.audits.void]] | ||
| 397 | who = "George Burgess IV <[email protected]>" | ||
| 398 | criteria = "safe-to-deploy" | ||
| 399 | version = "1.0.2" | ||
| 400 | aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" | ||
| 401 | |||
| 402 | [[audits.mozilla.audits.crunchy]] | ||
| 403 | who = "Erich Gubler <[email protected]>" | ||
| 404 | criteria = "safe-to-deploy" | ||
| 405 | version = "0.2.3" | ||
| 406 | aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" | ||
| 407 | |||
| 408 | [[audits.mozilla.audits.document-features]] | ||
| 409 | who = "Erich Gubler <[email protected]>" | ||
| 410 | criteria = "safe-to-deploy" | ||
| 411 | version = "0.2.8" | ||
| 412 | aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" | ||
| 413 | |||
| 414 | [[audits.mozilla.audits.document-features]] | ||
| 415 | who = "Erich Gubler <[email protected]>" | ||
| 416 | criteria = "safe-to-deploy" | ||
| 417 | delta = "0.2.8 -> 0.2.9" | ||
| 418 | aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" | ||
| 419 | |||
| 420 | [[audits.mozilla.audits.document-features]] | ||
| 421 | who = "Erich Gubler <[email protected]>" | ||
| 422 | criteria = "safe-to-deploy" | ||
| 423 | delta = "0.2.9 -> 0.2.10" | ||
| 424 | aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" | ||
| 425 | |||
| 426 | [[audits.mozilla.audits.document-features]] | ||
| 427 | who = "Teodor Tanasoaia <[email protected]>" | ||
| 428 | criteria = "safe-to-deploy" | ||
| 429 | delta = "0.2.10 -> 0.2.11" | ||
| 430 | aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" | ||
| 431 | |||
| 432 | [[audits.mozilla.audits.fnv]] | ||
| 433 | who = "Bobby Holley <[email protected]>" | ||
| 434 | criteria = "safe-to-deploy" | ||
| 435 | version = "1.0.7" | ||
| 436 | notes = "Simple hasher implementation with no unsafe code." | ||
| 437 | aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" | ||
| 438 | |||
| 439 | [[audits.mozilla.audits.half]] | ||
| 440 | who = "John M. Schanck <[email protected]>" | ||
| 441 | criteria = "safe-to-deploy" | ||
| 442 | version = "1.8.2" | ||
| 443 | notes = """ | ||
| 444 | This crate contains unsafe code for bitwise casts to/from binary16 floating-point | ||
| 445 | format. I've reviewed these and found no issues. There are no uses of ambient | ||
| 446 | capabilities. | ||
| 447 | """ | ||
| 448 | aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" | ||
| 449 | |||
| 450 | [[audits.mozilla.audits.half]] | ||
| 451 | who = "Erich Gubler <[email protected]>" | ||
| 452 | criteria = "safe-to-deploy" | ||
| 453 | delta = "1.8.2 -> 1.8.3" | ||
| 454 | aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" | ||
| 455 | |||
| 456 | [[audits.mozilla.audits.half]] | ||
| 457 | who = "Erich Gubler <[email protected]>" | ||
| 458 | criteria = "safe-to-deploy" | ||
| 459 | delta = "1.8.3 -> 2.5.0" | ||
| 460 | aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" | ||
| 461 | |||
| 462 | [[audits.mozilla.audits.litrs]] | ||
| 463 | who = "Erich Gubler <[email protected]>" | ||
| 464 | criteria = "safe-to-deploy" | ||
| 465 | version = "0.4.1" | ||
| 466 | aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" | ||
| 467 | |||
| 468 | [[audits.mozilla.audits.strsim]] | ||
| 469 | who = "Ben Dean-Kawamura <[email protected]>" | ||
| 470 | criteria = "safe-to-deploy" | ||
| 471 | delta = "0.10.0 -> 0.11.1" | ||
| 472 | aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" | ||
diff --git a/tools/run_and_attach_rtt.sh b/tools/run_and_attach_rtt.sh new file mode 100644 index 000000000..13041d06b --- /dev/null +++ b/tools/run_and_attach_rtt.sh | |||
| @@ -0,0 +1,24 @@ | |||
| 1 | #!/usr/bin/env bash | ||
| 2 | set -euo pipefail | ||
| 3 | |||
| 4 | ELF="${1:-target/thumbv8m.main-none-eabihf/debug/examples/hello}" | ||
| 5 | PROBE_ID="${2:-1fc9:0143:H3AYDQVQMTROB}" | ||
| 6 | CHIP="${3:-MCXA276}" | ||
| 7 | SPEED="${4:-1000}" | ||
| 8 | |||
| 9 | # 1) Flash & run using the existing run.sh (probe is in use only during this step) | ||
| 10 | ./run.sh "$ELF" | ||
| 11 | |||
| 12 | # 2) Give target a short moment to boot and set up RTT CB in RAM | ||
| 13 | sleep 0.5 | ||
| 14 | |||
| 15 | # 3) Attach RTT/defmt using probe-rs (no flashing) | ||
| 16 | exec probe-rs attach \ | ||
| 17 | --chip "$CHIP" \ | ||
| 18 | --probe "$PROBE_ID" \ | ||
| 19 | --protocol swd \ | ||
| 20 | --speed "$SPEED" \ | ||
| 21 | "$ELF" \ | ||
| 22 | --rtt-scan-memory \ | ||
| 23 | --log-format oneline | ||
| 24 | |||
diff --git a/tools/run_jlink_noblock.sh b/tools/run_jlink_noblock.sh new file mode 100644 index 000000000..3ea1f2b4b --- /dev/null +++ b/tools/run_jlink_noblock.sh | |||
| @@ -0,0 +1,76 @@ | |||
| 1 | #!/usr/bin/env bash | ||
| 2 | set -euo pipefail | ||
| 3 | |||
| 4 | ELF="${1:-}" | ||
| 5 | PROBE_ID="${2:-1366:0101:000600110607}" # default to your J-Link | ||
| 6 | CHIP="${3:-MCXA276}" | ||
| 7 | SPEED="${4:-1000}" | ||
| 8 | PORT="${PROBE_RS_GDB_PORT:-1337}" | ||
| 9 | |||
| 10 | if [[ -z "${ELF}" || ! -f "${ELF}" ]]; then | ||
| 11 | echo "Usage: $0 <elf> [probe-id] [chip] [speed-khz]" >&2 | ||
| 12 | exit 1 | ||
| 13 | fi | ||
| 14 | |||
| 15 | if ! command -v probe-rs >/dev/null 2>&1; then | ||
| 16 | echo "probe-rs not found (cargo install probe-rs --features cli)" >&2 | ||
| 17 | exit 1 | ||
| 18 | fi | ||
| 19 | if ! command -v gdb-multiarch >/dev/null 2>&1; then | ||
| 20 | echo "gdb-multiarch not found; install it (e.g., sudo apt install gdb-multiarch)." >&2 | ||
| 21 | exit 1 | ||
| 22 | fi | ||
| 23 | |||
| 24 | # Start probe-rs GDB server | ||
| 25 | SERVER_LOG=$(mktemp) | ||
| 26 | probe-rs gdb --chip "${CHIP}" --protocol swd --speed "${SPEED}" --non-interactive "${ELF}" --probe "${PROBE_ID}" \ | ||
| 27 | >"${SERVER_LOG}" 2>&1 & | ||
| 28 | GDB_SERVER_PID=$! | ||
| 29 | |||
| 30 | # Wait for readiness | ||
| 31 | for _ in {1..50}; do | ||
| 32 | if grep -q "Firing up GDB stub" "${SERVER_LOG}"; then break; fi | ||
| 33 | if grep -q "Connecting to the chip was unsuccessful" "${SERVER_LOG}"; then | ||
| 34 | echo "probe-rs gdb server failed. Log:" >&2 | ||
| 35 | sed -e 's/^/ /' "${SERVER_LOG}" >&2 || true | ||
| 36 | kill "${GDB_SERVER_PID}" 2>/dev/null || true | ||
| 37 | exit 1 | ||
| 38 | fi | ||
| 39 | sleep 0.1 | ||
| 40 | done | ||
| 41 | |||
| 42 | # GDB script: load, resume, detach | ||
| 43 | GDB_SCRIPT=$(mktemp) | ||
| 44 | cat >"${GDB_SCRIPT}" <<EOF | ||
| 45 | set pagination off | ||
| 46 | set confirm off | ||
| 47 | set mem inaccessible-by-default off | ||
| 48 | |||
| 49 | target remote :${PORT} | ||
| 50 | monitor halt | ||
| 51 | load | ||
| 52 | set language c | ||
| 53 | # Start clean from Reset vector in RAM | ||
| 54 | set {int}0xE000ED08 = 0x20000000 | ||
| 55 | set \$xpsr = 0x01000000 | ||
| 56 | set \$sp = 0x20020000 | ||
| 57 | set \$pc = Reset | ||
| 58 | monitor resume | ||
| 59 | detach | ||
| 60 | quit | ||
| 61 | EOF | ||
| 62 | |||
| 63 | # Run GDB to program and resume target, then exit (probe released) | ||
| 64 | if ! gdb-multiarch -q -x "${GDB_SCRIPT}" "${ELF}"; then | ||
| 65 | echo "GDB failed; server log:" >&2 | ||
| 66 | sed -e 's/^/ /' "${SERVER_LOG}" >&2 || true | ||
| 67 | kill "${GDB_SERVER_PID}" 2>/dev/null || true | ||
| 68 | exit 1 | ||
| 69 | fi | ||
| 70 | |||
| 71 | # Stop server now that we've detached | ||
| 72 | kill "${GDB_SERVER_PID}" 2>/dev/null || true | ||
| 73 | rm -f "${GDB_SCRIPT}" "${SERVER_LOG}" || true | ||
| 74 | |||
| 75 | echo "Flashed, resumed, and detached (probe free)." | ||
| 76 | |||
