diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000..b9315a5 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,26 @@ +# Commits that changed file headers, copyright lines or version constants across +# the whole tree. They carry no behavioural change, so `git blame` should look +# through them to the commit that last touched the code itself. +# +# GitHub applies this file automatically. To use it locally, run once: +# +# git config blame.ignoreRevsFile .git-blame-ignore-revs +# +# Add a commit here only when it is mechanical and repository-wide. A commit +# that changes behaviour does not belong in this file, however large it is. + +# 2024-01-29 Update copyright. +# 62 files +151863ad7c9bc0898649b73853bbcb54c85884e4 + +# 2026-03-05 Updated copyright headers and removed trailing whitespace +# 71 files +b9ba4a97bfb60cfbf1ed49919a135d4595d22275 + +# 2026-06-06 Added copyright headers to files missing them +# 10 files +123218cc51a55adc9b0c3a57e862206a6b8b4432 + +# 2026-09-15 Normalized the AI disclosure comment to one fixed line per file (#85) +# 15 files +a9283d3a81eeb1254ea8a30697a7b61301cef8eb diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..ff41be5 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: weekly + target-branch: dev diff --git a/.github/workflows/regression_test.yml b/.github/workflows/regression_test.yml index b4554ab..73de83c 100644 --- a/.github/workflows/regression_test.yml +++ b/.github/workflows/regression_test.yml @@ -1,25 +1,29 @@ -# This is a basic workflow that is manually triggered - name: regression_test -# Controls when the action will run. Triggers the workflow on push or pull request -# events but only for the master branch on: workflow_dispatch: push: - branches: [ master ] + branches: [dev, master] pull_request: - branches: [ master ] + branches: [dev, master] + +concurrency: + group: levelx-regression-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} -# A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: - # This workflow contains a single job called "linux_job" run_tests: permissions: contents: read issues: read checks: write pull-requests: write + # GitHub validates deployment permissions even when deployment is skipped. + # The reusable test job scopes its token separately. pages: write id-token: write - uses: eclipse-threadx/threadx/.github/workflows/regression_template.yml@master \ No newline at end of file + uses: eclipse-threadx/threadx/.github/workflows/regression_template.yml@b37cd4a81a1cb8c2ebefc438220ab7f009e13362 + with: + coverage_name: merged + coverage_thresholds: '68 100' + skip_deploy: ${{ github.ref != 'refs/heads/master' || github.event_name == 'pull_request' }} diff --git a/.github/workflows/repo_checks.yml b/.github/workflows/repo_checks.yml new file mode 100644 index 0000000..955e6dd --- /dev/null +++ b/.github/workflows/repo_checks.yml @@ -0,0 +1,63 @@ +############################################################################### +# Copyright (c) 2026 Eclipse ThreadX contributors +# +# This program and the accompanying materials are made available under the +# terms of the MIT License which is available at +# https://opensource.org/licenses/MIT. +# +# AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5). +# The AI-generated portions may be considered public domain (CC0-1.0) +# and not subject to the project's licence. The human contributor has +# reviewed and verified that the code is correct. +# +# SPDX-License-Identifier: MIT and CC0-1.0 +############################################################################### + +name: repo_checks + +# Repository-wide text checks. +# +# THIS LIVES IN ITS OWN WORKFLOW BECAUSE IT MUST NOT BE PATH-FILTERED, and a +# path filter is a property of a workflow rather than of a job. The check +# selects its input with `git ls-files` and scans every tracked file, so any +# file at all can carry a finding. +# +# That is not hypothetical here. The pass that introduced the fixed +# disclosure line covered source files only, and the drift it was meant to end +# survived for months afterwards in CMake files, toolchain files, shell and +# PowerShell scripts, a GDB script and a Visual Studio manifest -- precisely +# the files a source-path filter would have skipped. A workflow that gates no +# pull request anybody opens is worse than no workflow, because it looks like +# coverage. +# +# Cheap enough that running it on everything costs nothing worth measuring: it +# is grep over a repository this size, with no toolchain, no build and no +# cache. + +on: + # No `paths:` on either trigger, deliberately. See above. + push: + branches: [ dev, master ] + pull_request: + branches: [ dev, master ] + +# A second push to the same branch makes the first answer irrelevant. +concurrency: + group: repo-checks-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + checks: + runs-on: ubuntu-24.04 + steps: + # Actions are pinned to a commit SHA with the version in the trailing + # comment. A tag can be moved; a SHA cannot, which is what makes "which + # code ran in our CI" answerable from the repository. + - name: Check out the repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Check the AI disclosure comments + run: scripts/check_ai_disclosure.sh diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a327a0b..1065247 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,72 +1,350 @@ -# Contributing to Eclipse ThreadX +# Contributing to Eclipse ThreadX LevelX Thanks for your interest in this project. ## Project description -Eclipse ThreadX provides a vendor-neutral, open source, safety certified OS for -real-time applications published on under a permissive license. The Eclipse -ThreadX suite encompasses: -* ThreadX - advanced real-time operating system (RTOS) designed specifically for deeply embedded applications -* NetX Duo - advanced, industrial-grade TCP/IP network stack designed specifically for deeply embedded real-time and IoT applications -* FileX - high-performance, FAT-compatible file system that’s fully integrated with ThreadX kernel -* GUIX - provides a complete, embedded graphical user interface (GUI) library -* USBX - high-performance USB host, device, and on-the-go (OTG) embedded stack, that is fully integrated with ThreadX kernel -* LevelX - Flash Wear Leveling for FileX and stand-alone purposes -* GuiX Studio - design environment, facilitating the creation and maintenance of all graphical elements for GUIX -* TraceX - analysis tool that provides a graphical view of real-time system events to better understand the behavior of real-time systems +LevelX provides NAND and NOR flash wear levelling for embedded applications. It maps logical sectors to physical flash, spreads erase cycles across the media, and is designed to recover after interrupted updates. Applications can use LevelX through FileX or access logical sectors directly. The library also supports a standalone configuration. -Project site: https://projects.eclipse.org/projects/iot.threadx +Eclipse ThreadX provides a vendor-neutral, open source, safety-certified OS for real-time applications, published under a permissive license. The Eclipse ThreadX suite encompasses: + +* **ThreadX** - advanced real-time operating system (RTOS) designed specifically for deeply embedded applications +* **NetX Duo** - advanced, industrial-grade TCP/IP network stack designed specifically for deeply embedded real-time and IoT applications +* **FileX** - high-performance, FAT-compatible file system that is fully integrated with the ThreadX kernel +* **GUIX** - complete, embedded graphical user interface (GUI) library +* **GUIX Studio** - design environment, facilitating the creation and maintenance of all graphical elements for GUIX +* **USBX** - high-performance USB host, device, and on-the-go (OTG) embedded stack, fully integrated with the ThreadX kernel +* **LevelX** - flash wear levelling for FileX and stand-alone purposes +* **TraceX** - analysis tool that provides a graphical view of real-time system events to better understand the behaviour of real-time systems +* **ZoneX** - deterministic partitioning hypervisor for mixed-criticality embedded systems +* **SampleX** - samples and demos for the components above + +Project websites: + +* https://threadx.io +* https://projects.eclipse.org/projects/iot.threadx + +This file describes how to contribute to **LevelX**. General contribution conventions are shared across the Eclipse ThreadX repositories; build and test instructions differ by component. ## Terms of Use -This repository is subject to the Terms of Use of the Eclipse Foundation -https://www.eclipse.org/legal/termsofuse.php +This repository is subject to the Terms of Use of the Eclipse Foundation: https://www.eclipse.org/legal/termsofuse.php -## Developer resources +## New contributors -Information regarding source code management, builds, coding standards, and more. -https://projects.eclipse.org/projects/iot.threadx/developer +Welcome. Here is the shortest path from zero to a merged pull request. -The project maintains the following source code repositories +1. **Create an Eclipse Foundation account.** https://accounts.eclipse.org/user/register -* https://github.com/eclipse-threadx/.github -* https://github.com/eclipse-threadx/cmsis-packs -* https://github.com/eclipse-threadx/filex -* https://github.com/eclipse-threadx/getting-started -* https://github.com/eclipse-threadx/guix -* https://github.com/eclipse-threadx/levelx -* https://github.com/eclipse-threadx/netxduo -* https://github.com/eclipse-threadx/rtos-docs -* https://github.com/eclipse-threadx/samples -* https://github.com/eclipse-threadx/threadx -* https://github.com/eclipse-threadx/threadx-learn-samples -* https://github.com/eclipse-threadx/tracex -* https://github.com/eclipse-threadx/usbx + The email address on that account matters: it must be the same address you use as the `Author` of your Git commits. If the two do not match, the ECA check on your pull request will fail. + +2. **Sign the Eclipse Contributor Agreement (ECA).** See the section below. This is a one-time step and covers every Eclipse Foundation project. + +
+ +3. **Configure Git accordingly.** + + ``` + git config user.name "Your Name" + git config user.email "the-address-on-your-eclipse-account@example.org" + ``` + +4. **Pick something to work on.** Open issues are fair game, including ones nobody has assigned. Please leave a comment on the issue saying you intend to work on it, so two people do not solve the same problem twice. If an issue carries a `good first issue` label, it is a reasonable starting point. + +5. **Have an idea for a new feature? Discuss it first.** Open a thread in [GitHub Discussions](https://github.com/orgs/eclipse-threadx/discussions) or raise it on the [developer mailing list](https://accounts.eclipse.org/mailing-list/threadx-dev) before writing code. Flash mapping and recovery changes can affect stored data across many applications, so discuss the shape of a feature before submitting a finished implementation. + +Bug fixes and documentation corrections need no prior discussion. Send them straight in. ## Eclipse Development Process -This Eclipse Foundation open project is governed by the Eclipse Foundation -Development Process and operates under the terms of the Eclipse IP Policy. +This Eclipse Foundation open project is governed by the Eclipse Foundation Development Process and operates under the terms of the Eclipse IP Policy. * https://eclipse.org/projects/dev_process * https://www.eclipse.org/org/documents/Eclipse_IP_Policy.pdf ## Eclipse Contributor Agreement -In order to be able to contribute to Eclipse Foundation projects you must electronically sign the Eclipse Contributor Agreement (ECA). -https://www.eclipse.org/legal/ECA.php +In order to be able to contribute to Eclipse Foundation projects you must electronically sign the Eclipse Contributor Agreement (ECA): https://www.eclipse.org/legal/ECA.php + +The ECA provides the Eclipse Foundation with a permanent record that you agree that each of your contributions will comply with the commitments documented in the Developer Certificate of Origin (DCO). Having an ECA on file associated with the email address matching the "Author" field of your contribution's Git commits fulfills the DCO's requirement that you sign-off on your contributions. + +For more information, please see the Eclipse Committer Handbook: https://www.eclipse.org/projects/handbook/#resources-commit + +## Required tooling + +Build and test LevelX with CMake and Ninja. The root and regression projects require CMake 3.13 or later. The project reference compiler on Linux is GCC 14 with matching gcov. The regression project compiles with `-std=c99`, warnings as errors, and 32-bit host options, so install a matching multilib toolchain. `scripts/install.sh` installs the Ubuntu host test tools and pins `gcovr` 8.6 in a Python virtual environment; activate that environment before collecting coverage locally. + +LevelX can run with ThreadX and FileX, or in a standalone configuration. The root CMake project links ThreadX unless `LX_STANDALONE_ENABLE` is set and links FileX when `LX_ENABLE_FILE_SERVERS` is enabled. The Linux regression runner obtains pinned ThreadX and FileX revisions under `test/cmake/` and checks existing checkouts against those pins. The regression scripts target Linux; there is no Windows regression wrapper in this repository. + +## Building and testing + +The CMake regression suite lives under `test/cmake/`. The top-level scripts build and test all ten configurations: + +```sh +CC=gcc-14 GCOV=gcov-14 TX_COVERAGE=ON scripts/build.sh +CC=gcc-14 GCOV=gcov-14 TX_COVERAGE=ON scripts/test.sh +``` + +For a focused run, use the same configuration in both steps: + +```sh +test/cmake/run.sh build standalone_full_build +test/cmake/run.sh test standalone_full_build +``` + +The configurations cover the default build, free-sector verification, combined features, standalone operation, the newer NAND and NOR driver interfaces, and NOR mapping and obsolete-count caches. Select the configurations affected by your change; run the full suite before review when feasible. + +With `TX_COVERAGE=ON`, all ten configurations instrument the LevelX library and produce per-configuration JSON, XML and HTML reports under `test/cmake/coverage_report/`. The full suite merges their coverage. CI requires at least 68.0% line and 65.4% branch coverage in the merged report. These floors do not replace the project's 100% coverage goal: add or update regression tests for new behaviour and explain any relevant gaps. Host regression results do not replace testing a changed NAND or NOR driver on the affected flash hardware. + +## Continuous integration + +`regression_test.yml` runs on pushes and pull requests to `dev` and `master`, and by manual dispatch. It builds and tests all ten Linux configurations through the pinned ThreadX reusable regression workflow, then merges their coverage. It does not run Windows or flash hardware tests. + +Run affected flash hardware tests where available and report their results separately. Do not describe an unrun configuration or flash target as verified. + +## Pull request acceptance criteria + +**Pull requests must target the `dev` branch.** `master` holds the latest release; see [Release model and support](#release-model-and-support) below. A pull request opened against `master` will be asked to retarget. + +Before requesting a review, check your contribution against this list. + +**Process** + +* The branch is a feature branch based on `dev`. Never commit directly to `master` or `dev`. +* Your ECA is signed and the commit `Author` email matches your Eclipse account. +* The pull request is one logical change. Unrelated fixes belong in separate pull requests. +* Commit subject lines start with a past-tense verb, for example `Fixed the memory allocator`. +* The pull request explains what changed and why, and how you verified it. + +**Code** + +* The code is C99-compatible. +* It follows the coding style of the surrounding code. +* New functions and structures are documented in comments, as in existing code. +* MISRA C rules are followed as closely as practical, taking MISRA C 2004, 2012 and 2023 into account. Any deviation is explicit, names the rule being circumvented, and justifies it in a comment. +* `goto` is not used. +* Do not add new external dependencies. This is a hard rule. ThreadX and FileX are existing component relationships for the configured build. +* When implementing an industry standard, code is not copied from an existing implementation. Existing implementations may inform your work, but you must identify those sources clearly. +* Code is written with the suite's priorities in mind: runtime speed and small code size. +* New C and assembly files, and edited C and assembly files, carry the appropriate header and attribution (see below). + +**Verification** + +* All applicable CI checks are green, including the full Linux regression workflow on a pull request to `dev`. +* The change builds without new warnings on the reference toolchains. +* Regression tests covering the change are added or updated. The project targets 100% test coverage; a pull request that lowers coverage needs a stated reason. State which LevelX configurations ran and any relevant coverage gap. +* API or behaviour changes come with a matching documentation pull request against [rtos-docs-asciidoc](https://github.com/eclipse-threadx/rtos-docs-asciidoc). + +**Security** + +If you discover a security issue while working on a change, do not describe it in a public pull request. Follow [SECURITY.md](SECURITY.md) instead. + +## AI-assisted contributions + +**AI-assisted contributions are welcome**, provided they are attributed. + +Two things are non-negotiable: + +1. **Attribution.** Mark AI-assisted C and assembly files as described below, and identify the tool and model in the commit message. +2. **Human responsibility.** The human contributor submitting the pull request is responsible for the contribution - technically and legally. Signing the ECA means *you* certify the contribution's provenance. An AI tool cannot sign the ECA and cannot hold that responsibility. Review what the tool produced, understand it, and verify that it is correct and that you have the right to contribute it. "The model wrote it" is not a defence. + +This is consistent with the Eclipse Foundation's [Generative AI Usage Guidelines](https://www.eclipse.org/projects/guidelines/genai/) and the [Eclipse Project Handbook](https://www.eclipse.org/projects/handbook/#genai). Please read them before submitting AI-assisted work. + +### Using a coding agent + +Give a coding agent this contribution guide and the build instructions for the area it changes. Review its output against the same acceptance criteria as any other contribution. In particular, check C99 compatibility, surrounding style, test coverage, and the affected flash type, driver interface, and standalone or integrated configuration. + +### Header for new files + +Add this header when creating a new C or assembly (`.S`) file: + +```c +/*************************************************************************** + * Copyright (c) Eclipse ThreadX contributors + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * AI Disclosure: This file was largely AI-generated by (). + * The AI-generated portions may be considered public domain (CC0-1.0) + * and not subject to the project's licence. The human contributor has + * reviewed and verified that the code is correct. + * + * SPDX-License-Identifier: MIT and CC0-1.0 + **************************************************************************/ +``` + +Substitute the current year, product name, and model and version. If a file was written without AI assistance, omit the AI Disclosure paragraph and use `SPDX-License-Identifier: MIT`. + +### Header for existing files + +When editing an existing C or assembly file whose copyright is from 2025 or earlier, and which does not already mention Eclipse ThreadX contributors, add this line to the header: + +```c + * Copyright (c) Eclipse ThreadX contributors +``` + +It goes *below* the older copyright. All copyright lines must stay in chronological order. For example: + +```c +/*************************************************************************** + * Copyright (c) 2024 Microsoft Corporation + * Copyright (c) 2026 Eclipse ThreadX contributors + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * SPDX-License-Identifier: MIT + **************************************************************************/ +``` + +If the edit was AI-assisted and the file does not already carry an AI disclosure line, add this line just under the header: + +```c +// Portions of this file were generated with AI assistance. +``` + +Use the comment marker already used by the file. Keep an existing disclosure line unchanged and never add a second one. If you copy an existing file to get started on a new one, treat the result as a new file: use the new-file header, do not carry the old one over. -The ECA provides the Eclipse Foundation with a permanent record that you agree -that each of your contributions will comply with the commitments documented in -the Developer Certificate of Origin (DCO). Having an ECA on file associated with -the email address matching the "Author" field of your contribution's Git commits -fulfills the DCO's requirement that you sign-off on your contributions. +### Commit attribution -For more information, please see the Eclipse Committer Handbook: -https://www.eclipse.org/projects/handbook/#resources-commit +Attribute AI assistance with an `Assisted-by` trailer in each commit that contains AI-assisted changes: + +```text +Fixed the flash sector mapping status + +A completed mapping operation could report an out-of-date status. + +The mapping path now returns the status set by the completed operation. + +The affected LevelX regression configurations passed all tests. + +Assisted-by: () +``` + +Use the product's accepted name (`Claude Code`, `Copilot`, `Codex`, or `Gemini`) and the model string reported by the tool. Do not use `Co-Authored-By` for AI attribution. Commit subjects and pull request titles start with a past-tense verb and stay within 72 characters. The message body states the cause, fix, and test result in that order; wrap commit bodies at 88 characters. Pull request descriptions use one line per paragraph. + +## Release model and support + +This section summarises the project's [Release Model and Support Policy](https://github.com/eclipse-threadx/rtos-docs-asciidoc/blob/main/rtos-docs/home/modules/ROOT/pages/releases-and-support.adoc), which is the authoritative version. + +### Version numbers + +Eclipse ThreadX releases generally follow [Semantic Versioning](https://semver.org/). Given a version number **X.Y.Z.Bh**, for example `6.5.0.202601a`: + +* **X** increases for a *milestone release*. Components currently stay at version 6. +* **Y** increases for a *feature release* adding a major feature. +* **Z** increases for a *maintenance release* of minor fixes and improvements. +* **B** is a *build number* identifying the quarter of publication - `202601` is Q1 2026. +* **h** denotes a *hotfix release*, identified by a letter. Hotfix releases are component-specific. + +### Release cadence + +The project adopted a predictable quarterly release model in September 2025 and has published a release every quarter since. + +Quarterly releases ship new versions of every component, whether or not that component saw code changes, so that version numbers do not drift apart between components. A release shipping no code changes for a component says so in its release notes. + +Urgent fixes between quarterly releases ship as *hotfix* releases. Those contain only security fixes, or fixes for serious problems that prevent building or testing applications. + +### Branches + +The project runs a time-based release train on trunk-based development. There are no long-term support branches and no backports to older releases. + +* `master` always contains the latest quarterly or hotfix release. +* `dev` is the integration branch. Pull requests are merged there during the quarter. Track `dev` if you want to test new features and fixes ahead of a release. +* A quarterly release is performed by merging `dev` into `master`. + +### Support policy + +Issues in the codebase are fixed on a best-effort basis, and pull requests fixing existing or new issues are gladly accepted. + +Security vulnerabilities disclosed confidentially are handled under the [Eclipse Foundation's security policy](https://www.eclipse.org/security/policy/). Once a candidate vulnerability is confirmed, the team works to deliver a fix as soon as possible, shipping it in the next quarterly release or as a hotfix depending on timing. Resolved vulnerabilities are disclosed immediately after a release containing the fix becomes available. The team aims to resolve newly confirmed vulnerabilities within three months; that period may be extended by the Project Leadership Chain together with the Eclipse Foundation Security team where appropriate. + +See [SECURITY.md](SECURITY.md) for how to report a vulnerability. + +### Roadmap + +The project team plans its work on a [public GitHub project board](https://github.com/orgs/eclipse-threadx/projects/2/views/2). + +### A note on older releases + +Microsoft contributed only the Azure RTOS codebase v6.x to the Eclipse Foundation. Older ThreadX releases (v5.x and lower) sold by Express Logic were never made open source, and Microsoft has discontinued sales and support for them. Users of ThreadX 5.x and lower should upgrade to the latest release of Eclipse ThreadX as soon as possible. + +## Documentation + +The documentation is published at **https://threadx.io**. PDF manuals for every component, in A4 and US Letter formats, are attached as assets to each GitHub release. + +### The stack + +Documentation is written in [AsciiDoc](https://asciidoc.org/) and built with [Antora](https://antora.org/). PDF manuals are produced by the [Antora Assembler](https://docs.antora.org/assembler/latest/) with `asciidoctor-pdf`. + +### The workflow + +1. The single source of truth is [rtos-docs-asciidoc](https://github.com/eclipse-threadx/rtos-docs-asciidoc). All documentation changes are made there, as pull requests, following the same branch rules as the code repositories. +2. At release time the site is generated from that source into [rtos-docs-html](https://github.com/eclipse-threadx/rtos-docs-html), which holds the rendered HTML and exists solely for website integration. Do not edit it by hand - your changes will be overwritten by the next build. +3. The website then serves the generated content from `rtos-docs-html`. + +The older `rtos-docs` repository, which held the documentation in Markdown, is archived and superseded by `rtos-docs-asciidoc`. Do not send changes there. + +If your contribution adds or changes an API or a feature, open a matching pull request against `rtos-docs-asciidoc`. + +## Developer resources + +Information regarding source code management, builds, coding standards, and more: https://projects.eclipse.org/projects/iot.threadx/developer + +The project maintains the following repositories: + +**Components** + +* https://github.com/eclipse-threadx/threadx +* https://github.com/eclipse-threadx/netxduo +* https://github.com/eclipse-threadx/filex +* https://github.com/eclipse-threadx/guix +* https://github.com/eclipse-threadx/usbx +* https://github.com/eclipse-threadx/levelx +* https://github.com/eclipse-threadx/tracex +* https://github.com/eclipse-threadx/zonex + +**Samples and platforms** + +* https://github.com/eclipse-threadx/samplex +* https://github.com/eclipse-threadx/supported-platforms + +**Documentation** + +* https://github.com/eclipse-threadx/rtos-docs-asciidoc +* https://github.com/eclipse-threadx/rtos-docs-html + +**Community and process** + +* https://github.com/eclipse-threadx/discussions +* https://github.com/eclipse-threadx/trustedx +* https://github.com/eclipse-threadx/.github ## Contact -Contact the project developers via the project's "dev" list. +### GitHub Discussions + +https://github.com/orgs/eclipse-threadx/discussions + +Q&A, feedback, and announcements. Decisions taken by the project team are documented here as well. This is usually the fastest way to reach both the team and other users. + +### Main ThreadX mailing list + +https://accounts.eclipse.org/mailing-list/threadx + +News and updates about the ThreadX project and the ThreadX Alliance. + +### Developer mailing list + https://accounts.eclipse.org/mailing-list/threadx-dev + +Project team conversations. Feel free to jump in and ask non-technical questions there. + +### User mailing list + +https://accounts.eclipse.org/mailing-list/threadx-users + +Ask your technical questions and discuss issues here. diff --git a/README.md b/README.md index 2b19fae..3ee0462 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ LevelX provides NAND and NOR flash wear leveling facilities to embedded applicat LevelX as part of Eclipse ThreadX has been integrated to the semiconductor's SDKs and development environment. You can develop using the tools of choice from [STMicroelectronics](https://www.st.com/content/st_com/en/campaigns/x-cube-azrtos-azure-rtos-stm32.html), [NXP](https://www.nxp.com/design/software/embedded-software/azure-rtos-for-nxp-microcontrollers:AZURE-RTOS), [Renesas](https://github.com/renesas/azure-rtos) and [Microchip](https://mu.microchip.com/get-started-simplifying-your-iot-design-with-azure-rtos). -See [Overview of Eclipse ThreadX LevelX](https://github.com/eclipse-threadx/rtos-docs/blob/main/rtos-docs/levelx/index.md) for the high-level overview. +See [Overview of Eclipse ThreadX LevelX](https://threadx.io/releases/6.5.1/levelx/main/index.html) for the high-level overview. ## Repository Structure and Usage @@ -122,7 +122,7 @@ License terms for using Eclipse ThreadX are defined in the LICENSE.txt file of t The following are references to additional Eclipse ThreadX resources: -- **Product introduction**: https://github.com/eclipse-threadx/rtos-docs +- **Product introduction**: https://threadx.io/releases/latest - **Product issues and bugs, or feature requests**: https://github.com/eclipse-threadx/levelx/issues - **TraceX Installer**: https://aka.ms/azrtos-tracex-installer diff --git a/common/inc/lx_api.h b/common/inc/lx_api.h index 4d5cf11..1a51b17 100644 --- a/common/inc/lx_api.h +++ b/common/inc/lx_api.h @@ -10,6 +10,8 @@ * SPDX-License-Identifier: MIT **************************************************************************/ +// Portions of this file were generated with AI assistance. + /**************************************************************************/ /**************************************************************************/ @@ -149,8 +151,8 @@ typedef unsigned long long ULONG64; #define AZURE_RTOS_LEVELX #define LEVELX_MAJOR_VERSION 6 #define LEVELX_MINOR_VERSION 5 -#define LEVELX_PATCH_VERSION 1 -#define LEVELX_BUILD_VERSION 202602 +#define LEVELX_PATCH_VERSION 2 +#define LEVELX_BUILD_VERSION 202603 #define LEVELX_HOTFIX_VERSION ' ' @@ -537,6 +539,7 @@ typedef struct LX_NOR_FLASH_EXTENDED_CACHE_ENTRY_STRUCT ULONG *lx_nor_flash_extended_cache_entry_sector_address; ULONG *lx_nor_flash_extended_cache_entry_sector_memory; ULONG lx_nor_flash_extended_cache_entry_access_count; + UINT lx_nor_flash_extended_cache_entry_valid; } LX_NOR_FLASH_EXTENDED_CACHE_ENTRY; @@ -814,4 +817,3 @@ VOID _lx_nor_flash_system_error(LX_NOR_FLASH *nor_flash, UINT error_code); #endif #endif - diff --git a/common/src/lx_nand_flash_block_allocate.c b/common/src/lx_nand_flash_block_allocate.c index ff3407d..551030e 100644 --- a/common/src/lx_nand_flash_block_allocate.c +++ b/common/src/lx_nand_flash_block_allocate.c @@ -9,7 +9,7 @@ * SPDX-License-Identifier: MIT **************************************************************************/ -// Some portions generated by Copilot (Sonnet 4.6). +// Portions of this file were generated with AI assistance. /**************************************************************************/ /**************************************************************************/ diff --git a/common/src/lx_nand_flash_block_data_move.c b/common/src/lx_nand_flash_block_data_move.c index e265243..46ca11c 100644 --- a/common/src/lx_nand_flash_block_data_move.c +++ b/common/src/lx_nand_flash_block_data_move.c @@ -9,7 +9,7 @@ * SPDX-License-Identifier: MIT **************************************************************************/ -// Some portions generated by Copilot (Sonnet 4.6). +// Portions of this file were generated with AI assistance. /**************************************************************************/ /**************************************************************************/ diff --git a/common/src/lx_nand_flash_defragment.c b/common/src/lx_nand_flash_defragment.c index 8afced7..a5fdb52 100644 --- a/common/src/lx_nand_flash_defragment.c +++ b/common/src/lx_nand_flash_defragment.c @@ -9,7 +9,7 @@ * SPDX-License-Identifier: MIT **************************************************************************/ -// Some portions generated by Copilot (Sonnet 4.6). +// Portions of this file were generated with AI assistance. /**************************************************************************/ /**************************************************************************/ diff --git a/common/src/lx_nand_flash_logical_group_compact.c b/common/src/lx_nand_flash_logical_group_compact.c index 1e71221..8d1d0cc 100644 --- a/common/src/lx_nand_flash_logical_group_compact.c +++ b/common/src/lx_nand_flash_logical_group_compact.c @@ -8,7 +8,7 @@ * SPDX-License-Identifier: MIT **************************************************************************/ -// Some portions generated by Copilot (Sonnet 4.6). +// Portions of this file were generated with AI assistance. /**************************************************************************/ /**************************************************************************/ diff --git a/common/src/lx_nand_flash_memory_initialize.c b/common/src/lx_nand_flash_memory_initialize.c index 92143d4..af8f444 100644 --- a/common/src/lx_nand_flash_memory_initialize.c +++ b/common/src/lx_nand_flash_memory_initialize.c @@ -9,7 +9,7 @@ * SPDX-License-Identifier: MIT **************************************************************************/ -// Some portions generated by Copilot (Sonnet 4.6). +// Portions of this file were generated with AI assistance. /**************************************************************************/ /**************************************************************************/ diff --git a/common/src/lx_nand_flash_open_extended.c b/common/src/lx_nand_flash_open_extended.c index 69f97d5..282a21d 100644 --- a/common/src/lx_nand_flash_open_extended.c +++ b/common/src/lx_nand_flash_open_extended.c @@ -9,7 +9,7 @@ * SPDX-License-Identifier: MIT **************************************************************************/ -// Some portions generated by Copilot (Sonnet 4.6). +// Portions of this file were generated with AI assistance. /**************************************************************************/ /**************************************************************************/ diff --git a/common/src/lx_nand_flash_sector_read.c b/common/src/lx_nand_flash_sector_read.c index aa537dc..2e181b5 100644 --- a/common/src/lx_nand_flash_sector_read.c +++ b/common/src/lx_nand_flash_sector_read.c @@ -9,7 +9,7 @@ * SPDX-License-Identifier: MIT **************************************************************************/ -// Some portions generated by Copilot (Sonnet 4.6). +// Portions of this file were generated with AI assistance. /**************************************************************************/ /**************************************************************************/ diff --git a/common/src/lx_nand_flash_sector_release.c b/common/src/lx_nand_flash_sector_release.c index 9783442..ea4a55f 100644 --- a/common/src/lx_nand_flash_sector_release.c +++ b/common/src/lx_nand_flash_sector_release.c @@ -9,7 +9,7 @@ * SPDX-License-Identifier: MIT **************************************************************************/ -// Some portions generated by Copilot (Sonnet 4.6). +// Portions of this file were generated with AI assistance. /**************************************************************************/ /**************************************************************************/ diff --git a/common/src/lx_nand_flash_sector_write.c b/common/src/lx_nand_flash_sector_write.c index a625b53..4afca76 100644 --- a/common/src/lx_nand_flash_sector_write.c +++ b/common/src/lx_nand_flash_sector_write.c @@ -9,7 +9,7 @@ * SPDX-License-Identifier: MIT **************************************************************************/ -// Some portions generated by Copilot (Sonnet 4.6). +// Portions of this file were generated with AI assistance. /**************************************************************************/ /**************************************************************************/ diff --git a/common/src/lx_nor_flash_block_reclaim.c b/common/src/lx_nor_flash_block_reclaim.c index e2951f1..096be34 100644 --- a/common/src/lx_nor_flash_block_reclaim.c +++ b/common/src/lx_nor_flash_block_reclaim.c @@ -9,6 +9,8 @@ * SPDX-License-Identifier: MIT **************************************************************************/ +/* Portions of this file were generated with AI assistance. */ + /**************************************************************************/ /**************************************************************************/ @@ -294,7 +296,8 @@ UINT status; _lx_nor_flash_physical_sector_allocate(nor_flash, logical_sector, &new_mapping_address, &new_sector_address); /* Check to see if the new sector is also in the erase block. */ - if ((new_sector_address >= block_word_ptr) && (new_sector_address < (block_word_ptr + nor_flash -> lx_nor_flash_words_per_block))) + if ((new_mapping_address != LX_NULL) && (new_sector_address >= block_word_ptr) && + (new_sector_address < (block_word_ptr + nor_flash -> lx_nor_flash_words_per_block))) { /* Yes, the new sector was found in the block to be erased. Simply move the search pointer @@ -311,7 +314,8 @@ UINT status; /* Check again for the new sector inside of the block to erase. This should be impossible, since we check previously if there are enough free sectors outside of this block needed to reclaim this block. */ - if ((new_sector_address >= block_word_ptr) && (new_sector_address < (block_word_ptr + LX_NOR_SECTOR_SIZE))) + if ((new_mapping_address != LX_NULL) && (new_sector_address >= block_word_ptr) && + (new_sector_address < (block_word_ptr + LX_NOR_SECTOR_SIZE))) { /* System error, a new sector is not available outside of the erase block. @@ -453,7 +457,7 @@ UINT status; _lx_nor_flash_system_error(nor_flash, LX_SYSTEM_ALLOCATION_FAILED); /* Return the error. */ - return(status); + return(LX_SYSTEM_ALLOCATION_FAILED); } /* Decrement the number of mapped sectors. */ diff --git a/common/src/lx_nor_flash_driver_block_erase.c b/common/src/lx_nor_flash_driver_block_erase.c index d5e4b46..e410046 100644 --- a/common/src/lx_nor_flash_driver_block_erase.c +++ b/common/src/lx_nor_flash_driver_block_erase.c @@ -9,6 +9,8 @@ * SPDX-License-Identifier: MIT **************************************************************************/ +// Portions of this file were generated with AI assistance. + /**************************************************************************/ /**************************************************************************/ @@ -76,15 +78,19 @@ UINT status; #ifndef LX_NOR_DISABLE_EXTENDED_CACHE UINT i; -ULONG *block_start_address; -ULONG *block_end_address; +ULONG block_start_address; +ULONG block_end_address; ULONG *cache_entry_start; -ULONG *cache_entry_end; +ULONG cache_entry_start_value; +ULONG cache_entry_end_value; - /* Calculate the block starting address. */ - block_start_address = nor_flash -> lx_nor_flash_base_address + (block * nor_flash -> lx_nor_flash_words_per_block); - block_end_address = block_start_address + nor_flash -> lx_nor_flash_words_per_block; + /* Calculate the block starting address. + MISRA C:2012 Rule 11.4 deviation: NOR driver addresses may be logical + address tokens, including zero, so compare address values without + dereferencing them. */ + block_start_address = (ULONG)(nor_flash -> lx_nor_flash_base_address) + (block * nor_flash -> lx_nor_flash_words_per_block * sizeof(ULONG)); + block_end_address = block_start_address + (nor_flash -> lx_nor_flash_words_per_block * sizeof(ULONG)); /* Loop through the cache entries to see if there is a sector in cache. */ for (i = 0; i < nor_flash -> lx_nor_flash_extended_cache_entries; i++) @@ -94,15 +100,21 @@ ULONG *cache_entry_end; /* Determine the cache entry addresses. */ cache_entry_start = nor_flash -> lx_nor_flash_extended_cache[i].lx_nor_flash_extended_cache_entry_sector_address; - cache_entry_end = cache_entry_start + LX_NOR_SECTOR_SIZE; - /* Determine if the flash address in in the cache entry. */ - if ((cache_entry_start) && (block_start_address <= cache_entry_start) && (block_end_address > cache_entry_end)) + /* Determine if the cache entry is in the block being erased. */ + if (nor_flash -> lx_nor_flash_extended_cache[i].lx_nor_flash_extended_cache_entry_valid) { + cache_entry_start_value = (ULONG)cache_entry_start; + cache_entry_end_value = cache_entry_start_value + (LX_NOR_SECTOR_SIZE * sizeof(ULONG)); + + if ((block_start_address <= cache_entry_start_value) && (block_end_address >= cache_entry_end_value)) + { - /* Yes, this cache entry is in the block to be erased so invalidate it. */ - nor_flash -> lx_nor_flash_extended_cache[i].lx_nor_flash_extended_cache_entry_sector_address = LX_NULL; - nor_flash -> lx_nor_flash_extended_cache[i].lx_nor_flash_extended_cache_entry_access_count = 0; + /* Yes, this cache entry is in the block to be erased so invalidate it. */ + nor_flash -> lx_nor_flash_extended_cache[i].lx_nor_flash_extended_cache_entry_sector_address = LX_NULL; + nor_flash -> lx_nor_flash_extended_cache[i].lx_nor_flash_extended_cache_entry_access_count = 0; + nor_flash -> lx_nor_flash_extended_cache[i].lx_nor_flash_extended_cache_entry_valid = LX_FALSE; + } } } #endif @@ -117,5 +129,3 @@ ULONG *cache_entry_end; /* Return completion status. */ return(status); } - - diff --git a/common/src/lx_nor_flash_driver_read.c b/common/src/lx_nor_flash_driver_read.c index 5a0e3d9..5f2f0e5 100644 --- a/common/src/lx_nor_flash_driver_read.c +++ b/common/src/lx_nor_flash_driver_read.c @@ -9,6 +9,8 @@ * SPDX-License-Identifier: MIT **************************************************************************/ +// Portions of this file were generated with AI assistance. + /**************************************************************************/ /**************************************************************************/ @@ -76,8 +78,11 @@ UINT _lx_nor_flash_driver_read(LX_NOR_FLASH *nor_flash, ULONG *flash_address, U UINT status; UINT i; ULONG *cache_entry_start; -ULONG *cache_entry_end; +ULONG cache_entry_start_value; +ULONG cache_entry_end_value; ULONG cache_offset; +ULONG flash_address_value; +ULONG base_address_value; UINT least_used_cache_entry; @@ -91,6 +96,11 @@ UINT least_used_cache_entry; /* Initialize the least used cache entry. */ least_used_cache_entry = 0; + /* MISRA C:2012 Rule 11.4 deviation: NOR driver addresses may be logical + address tokens, including zero, so compare address values without + dereferencing them. */ + flash_address_value = (ULONG)flash_address; + do { @@ -102,52 +112,55 @@ UINT least_used_cache_entry; /* Determine the cache entry addresses. */ cache_entry_start = nor_flash -> lx_nor_flash_extended_cache[i].lx_nor_flash_extended_cache_entry_sector_address; - cache_entry_end = cache_entry_start + LX_NOR_SECTOR_SIZE; - /* Determine if the flash address in in the cache entry. */ - if ((cache_entry_start) && (flash_address >= cache_entry_start) && (flash_address < cache_entry_end)) + /* Determine if the flash address is in the cache entry. */ + if (nor_flash -> lx_nor_flash_extended_cache[i].lx_nor_flash_extended_cache_entry_valid) { + cache_entry_start_value = (ULONG)cache_entry_start; + cache_entry_end_value = cache_entry_start_value + (LX_NOR_SECTOR_SIZE * sizeof(ULONG)); - /* Yes, we found the entry. */ + if ((flash_address_value >= cache_entry_start_value) && (flash_address_value < cache_entry_end_value)) + { - /* Increment the accessed count. */ - nor_flash -> lx_nor_flash_extended_cache[i].lx_nor_flash_extended_cache_entry_access_count++; + /* Yes, we found the entry. */ - /* Calculate the offset into the cache entry. */ - cache_offset = (ULONG)(flash_address - cache_entry_start); + /* Increment the accessed count. */ + nor_flash -> lx_nor_flash_extended_cache[i].lx_nor_flash_extended_cache_entry_access_count++; - /* Copy the word from the cache. */ - *destination = *(nor_flash -> lx_nor_flash_extended_cache[i].lx_nor_flash_extended_cache_entry_sector_memory + cache_offset); + /* Calculate the offset into the cache entry. */ + cache_offset = (flash_address_value - cache_entry_start_value) / sizeof(ULONG); - /* Increment the number of cache hits. */ - nor_flash -> lx_nor_flash_extended_cache_hits++; + /* Copy the word from the cache. */ + *destination = *(nor_flash -> lx_nor_flash_extended_cache[i].lx_nor_flash_extended_cache_entry_sector_memory + cache_offset); - /* Return success. */ - return(LX_SUCCESS); + /* Increment the number of cache hits. */ + nor_flash -> lx_nor_flash_extended_cache_hits++; + + /* Return success. */ + return(LX_SUCCESS); + } } - else + + /* Determine if we have a new least used sector. */ + if (i != least_used_cache_entry) { - /* Determine if we have a new least used sector. */ - if (i != least_used_cache_entry) + /* Determine if this entry has a smaller accessed count. */ + if (nor_flash -> lx_nor_flash_extended_cache[i].lx_nor_flash_extended_cache_entry_access_count < + nor_flash -> lx_nor_flash_extended_cache[least_used_cache_entry].lx_nor_flash_extended_cache_entry_access_count) { - /* Determine if this entry has a smaller accessed count. */ - if (nor_flash -> lx_nor_flash_extended_cache[i].lx_nor_flash_extended_cache_entry_access_count < - nor_flash -> lx_nor_flash_extended_cache[least_used_cache_entry].lx_nor_flash_extended_cache_entry_access_count) - { - - /* New least used entry. */ - least_used_cache_entry = i; - } + /* New least used entry. */ + least_used_cache_entry = i; } } } /* Now read in the sector into the cache. */ - cache_offset = (ULONG)(flash_address - nor_flash -> lx_nor_flash_base_address); + base_address_value = (ULONG)(nor_flash -> lx_nor_flash_base_address); + cache_offset = (flash_address_value - base_address_value) / sizeof(ULONG); cache_offset = cache_offset & ~((ULONG) (LX_NOR_SECTOR_SIZE-1)); - cache_entry_start = nor_flash -> lx_nor_flash_base_address + cache_offset; + cache_entry_start = (ULONG *)(base_address_value + (cache_offset * sizeof(ULONG))); /* Call the actual driver read function. */ #ifdef LX_NOR_ENABLE_CONTROL_BLOCK_FOR_DRIVER_INTERFACE @@ -171,6 +184,7 @@ UINT least_used_cache_entry; /* Setup the cache entry. */ nor_flash -> lx_nor_flash_extended_cache[least_used_cache_entry].lx_nor_flash_extended_cache_entry_sector_address = cache_entry_start; nor_flash -> lx_nor_flash_extended_cache[least_used_cache_entry].lx_nor_flash_extended_cache_entry_access_count = 0; + nor_flash -> lx_nor_flash_extended_cache[least_used_cache_entry].lx_nor_flash_extended_cache_entry_valid = LX_TRUE; /* Increment the number of cache misses. */ nor_flash -> lx_nor_flash_extended_cache_misses++; @@ -211,5 +225,3 @@ UINT status; return(status); #endif } - - diff --git a/common/src/lx_nor_flash_driver_write.c b/common/src/lx_nor_flash_driver_write.c index ac25d27..96c3ec5 100644 --- a/common/src/lx_nor_flash_driver_write.c +++ b/common/src/lx_nor_flash_driver_write.c @@ -9,6 +9,8 @@ * SPDX-License-Identifier: MIT **************************************************************************/ +// Portions of this file were generated with AI assistance. + /**************************************************************************/ /**************************************************************************/ @@ -77,8 +79,10 @@ UINT _lx_nor_flash_driver_write(LX_NOR_FLASH *nor_flash, ULONG *flash_address, UINT status; UINT i; ULONG *cache_entry_start; -ULONG *cache_entry_end; +ULONG cache_entry_start_value; +ULONG cache_entry_end_value; ULONG cache_offset; +ULONG flash_address_value; /* Is the request a whole sector or a partial sector. */ @@ -87,6 +91,11 @@ ULONG cache_offset; /* One word request, which implies that it is a NOR flash metadata write. */ + /* MISRA C:2012 Rule 11.4 deviation: NOR driver addresses may be logical + address tokens, including zero, so compare address values without + dereferencing them. */ + flash_address_value = (ULONG)flash_address; + /* Loop through the cache entries to see if there is a sector in cache. */ for (i = 0; i < nor_flash -> lx_nor_flash_extended_cache_entries; i++) { @@ -95,22 +104,27 @@ ULONG cache_offset; /* Determine the cache entry addresses. */ cache_entry_start = nor_flash -> lx_nor_flash_extended_cache[i].lx_nor_flash_extended_cache_entry_sector_address; - cache_entry_end = cache_entry_start + LX_NOR_SECTOR_SIZE; - /* Determine if the flash address in in the cache entry. */ - if ((cache_entry_start) && (flash_address >= cache_entry_start) && (flash_address < cache_entry_end)) + /* Determine if the flash address is in the cache entry. */ + if (nor_flash -> lx_nor_flash_extended_cache[i].lx_nor_flash_extended_cache_entry_valid) { + cache_entry_start_value = (ULONG)cache_entry_start; + cache_entry_end_value = cache_entry_start_value + (LX_NOR_SECTOR_SIZE * sizeof(ULONG)); - /* Yes, we found the entry. */ + if ((flash_address_value >= cache_entry_start_value) && (flash_address_value < cache_entry_end_value)) + { - /* Calculate the offset into the cache entry. */ - cache_offset = (ULONG)(flash_address - cache_entry_start); + /* Yes, we found the entry. */ - /* Copy the word into the cache. */ - *(nor_flash -> lx_nor_flash_extended_cache[i].lx_nor_flash_extended_cache_entry_sector_memory + cache_offset) = *source; + /* Calculate the offset into the cache entry. */ + cache_offset = (flash_address_value - cache_entry_start_value) / sizeof(ULONG); - /* Get out of the loop. */ - break; + /* Copy the word into the cache. */ + *(nor_flash -> lx_nor_flash_extended_cache[i].lx_nor_flash_extended_cache_entry_sector_memory + cache_offset) = *source; + + /* Get out of the loop. */ + break; + } } } } @@ -140,4 +154,3 @@ UINT status; return(status); #endif } - diff --git a/common/src/lx_nor_flash_extended_cache_enable.c b/common/src/lx_nor_flash_extended_cache_enable.c index 7bd2863..0c70fb9 100644 --- a/common/src/lx_nor_flash_extended_cache_enable.c +++ b/common/src/lx_nor_flash_extended_cache_enable.c @@ -9,6 +9,8 @@ * SPDX-License-Identifier: MIT **************************************************************************/ +// Portions of this file were generated with AI assistance. + /**************************************************************************/ /**************************************************************************/ @@ -93,8 +95,11 @@ ULONG block_word; #endif + /* Calculate cache size in words. */ + cache_size = size / sizeof(ULONG); + /* Determine if memory was specified but with an invalid size (less than one NOR sector). */ - if ((memory) && (size < LX_NOR_SECTOR_SIZE)) + if ((memory) && (cache_size < LX_NOR_SECTOR_SIZE)) { /* Error in memory size supplied. */ @@ -110,9 +115,6 @@ ULONG block_word; /* Initialize the internal NOR cache. */ nor_flash -> lx_nor_flash_extended_cache_entries = 0; - /* Calculate cache size in words. */ - cache_size = size/sizeof(ULONG); - /* Setup cache memory pointer. */ cache_memory = (ULONG *) memory; @@ -276,6 +278,7 @@ ULONG block_word; nor_flash -> lx_nor_flash_extended_cache[i].lx_nor_flash_extended_cache_entry_sector_address = LX_NULL; nor_flash -> lx_nor_flash_extended_cache[i].lx_nor_flash_extended_cache_entry_sector_memory = cache_memory; nor_flash -> lx_nor_flash_extended_cache[i].lx_nor_flash_extended_cache_entry_access_count = 0; + nor_flash -> lx_nor_flash_extended_cache[i].lx_nor_flash_extended_cache_entry_valid = LX_FALSE; /* Move the cache memory forward. */ cache_memory = cache_memory + LX_NOR_SECTOR_SIZE; @@ -317,5 +320,3 @@ ULONG block_word; return(LX_DISABLED); #endif } - - diff --git a/common/src/lx_nor_flash_next_block_to_erase_find.c b/common/src/lx_nor_flash_next_block_to_erase_find.c index 77ac3e6..9b8eaa6 100644 --- a/common/src/lx_nor_flash_next_block_to_erase_find.c +++ b/common/src/lx_nor_flash_next_block_to_erase_find.c @@ -470,4 +470,5 @@ UINT mapped_sectors_available = LX_FALSE; } /* Return success. */ return(LX_SUCCESS); -} \ No newline at end of file +} + diff --git a/scripts/build.sh b/scripts/build.sh index 0ec3d05..5b78ef2 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -10,4 +10,6 @@ # SPDX-License-Identifier: MIT ############################################################################## -$(dirname `realpath $0`)/../test/cmake/run.sh build all \ No newline at end of file +set -euo pipefail + +exec "$(dirname "$(realpath "$0")")/../test/cmake/run.sh" build all diff --git a/scripts/check_ai_disclosure.sh b/scripts/check_ai_disclosure.sh new file mode 100755 index 0000000..fe74397 --- /dev/null +++ b/scripts/check_ai_disclosure.sh @@ -0,0 +1,121 @@ +#!/bin/bash +############################################################################### +# Copyright (c) 2026 Eclipse ThreadX contributors +# +# This program and the accompanying materials are made available under the +# terms of the MIT License which is available at +# https://opensource.org/licenses/MIT. +# +# AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5). +# The AI-generated portions may be considered public domain (CC0-1.0) +# and not subject to the project's licence. The human contributor has +# reviewed and verified that the code is correct. +# +# SPDX-License-Identifier: MIT and CC0-1.0 +############################################################################### +# +# Fail the build if a file's AI disclosure comment has drifted from the one +# accepted form. +# +# A file that was edited with AI assistance carries exactly one line: +# +# Portions of this file were generated with AI assistance. +# +# written with the comment character that file already uses. It names no +# product, no model and no version, and a file carries at most one of it, ever. +# +# The text is fixed for a reason that is easy to miss. An earlier convention +# named the product and the model -- "Some portions generated by +# ()" -- and the result was not attribution but accumulation: each tool +# that touched a file failed to recognise the line another tool had left, and +# appended its own. Files reached three stacked lines, and one product ended +# up spelled four different ways across the tree, which made the record +# unusable for the one question it was meant to answer. +# +# Precise attribution lives on the commit instead, where the Assisted-by +# trailer is dated and attached to the diff it describes: +# +# git log --format='%h %(trailers:key=Assisted-by,valueonly)' -- +# +# A file-level flag answers WHETHER; the history answers WHO. A header line +# cannot hold the second honestly, because the code it names gets rewritten +# and the line stays. +# +# The AI Disclosure paragraph in a new file's copyright header is different and +# is not checked here: it keeps its product and model version, because a file +# is created once and that record cannot grow. +# +# This script is excluded from its own scan. It has to spell the rejected +# forms in order to look for them. + +set -euo pipefail + +readonly ROOT="$(cd "$(dirname "$(realpath "$0")")/.." && pwd)" +readonly SELF='scripts/check_ai_disclosure.sh' +readonly FIXED='Portions of this file were generated with AI assistance.' + +cd "${ROOT}" + +# Tracked files only. A build tree is not this repository's text to police, +# and scanning one would make the check depend on whether somebody had built. +mapfile -d '' -t FILES < <(git ls-files -z | grep -zZv "^${SELF}$") + +status=0 + +report() { + printf '%s\n\n' "$1" >&2 + printf '%s\n\n' "$2" >&2 + status=1 +} + +# 1. The superseded per-edit form, which names a product and a model. +hits="$(grep -nI 'Some portions generated by' -- "${FILES[@]}" 2>/dev/null || true)" +if [ -n "${hits}" ]; then + report "AI disclosure check FAILED: superseded per-edit form. + +Replace each of these with the fixed line, keeping the file's comment +character: + + ${FIXED}" "${hits}" +fi + +# 2. A doubled comment marker, such as '; //' or '@ //'. Assembly dialects +# differ -- armasm and IAR use ';', GNU as uses '@' or '//' -- and writing +# both is a symptom of a tool guessing rather than reading the file. +hits="$(grep -nIE '(//|[;@#])[[:space:]]*//[[:space:]]*Portions of this file were generated' \ + -- "${FILES[@]}" 2>/dev/null || true)" +if [ -n "${hits}" ]; then + report "AI disclosure check FAILED: doubled comment marker. + +Use the single comment character the rest of the file uses." "${hits}" +fi + +# 3. More than one disclosure line in a file. This is the failure the fixed +# text exists to prevent, so it is worth catching directly rather than +# inferring it from the form. +hits="$(grep -cIF "${FIXED}" -- "${FILES[@]}" 2>/dev/null | awk -F: '$NF > 1' || true)" +if [ -n "${hits}" ]; then + report "AI disclosure check FAILED: more than one disclosure line. + +A file carries at most one, ever. Keep the first and delete the rest; the +commit trailer, not the header, records which agents have touched the file." "${hits}" +fi + +# 4. A near miss. A line that is clearly meant to be the disclosure but is +# not spelled exactly right defeats every deduplication that follows it. +hits="$(grep -nIE '^[[:space:]]*(//|/\*+|\*|;|@|#)[[:space:]]*(Portions? of this file|Some portions).*AI assistance' \ + -- "${FILES[@]}" 2>/dev/null \ + | grep -vF "${FIXED}" || true)" +if [ -n "${hits}" ]; then + report "AI disclosure check FAILED: the text is not spelled exactly. + +The accepted text, character for character, is: + + ${FIXED}" "${hits}" +fi + +if [ "${status}" -eq 0 ]; then + echo "AI disclosure check passed." +fi + +exit "${status}" diff --git a/scripts/install.sh b/scripts/install.sh index bcf4a0c..17575bc 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -10,30 +10,49 @@ # SPDX-License-Identifier: MIT ############################################################################## -# +set -euo pipefail + +# Retry transient package and network failures a bounded number of times. +retry() { + local attempt + for attempt in 1 2 3; do + if "$@"; then + return 0 + fi + if [ "$attempt" -lt 3 ]; then + sleep $((attempt * 5)) + fi + done + return 1 +} -# Remove large folder -rm -rf /opt/hostedtoolcache +apt_options=(-o Acquire::Retries=3 -o DPkg::Lock::Timeout=60) +if ! retry sudo timeout 150 apt-get "${apt_options[@]}" update; then + echo "Package index update failed; package installation will verify availability." >&2 +fi +retry sudo timeout 150 apt-get "${apt_options[@]}" install -y \ + cmake gcc-14 gcc-14-multilib git ninja-build python3-venv \ + unifdef p7zip-full tofrodos gawk -# Install necessary softwares for Ubuntu. +venv_dir="${RUNNER_TEMP:-${TMPDIR:-/tmp}}/levelx-ci-venv" +python3 -m venv "$venv_dir" +retry timeout 120 "$venv_dir/bin/python" -m pip install \ + --retries 3 --timeout 30 gcovr==8.6 -sudo apt-get update -sudo apt-get install -y \ - gcc-multilib \ - git \ - g++ \ - python3-pip \ - ninja-build \ - unifdef \ - p7zip-full \ - tofrodos \ - gawk \ - software-properties-common +cc="${CC:-gcc-14}" +gcov="${GCOV:-gcov-14}" +cc_version=$("$cc" -dumpfullversion) +gcov_version=$("$gcov" --version | sed -n '1{s/.* \([0-9][0-9]*\.[0-9][0-9]*\(\.[0-9][0-9]*\)\?\).*/\1/p;}') +if [ -z "$gcov_version" ] || [ "$cc_version" != "$gcov_version" ]; then + echo "Compiler $cc and coverage tool $gcov have different versions." >&2 + exit 1 +fi -wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | sudo apt-key add - -CODENAME=$(lsb_release -c | cut -f2 -d':' | sed 's/\t//') -apt-add-repository "deb https://apt.kitware.com/ubuntu/ $CODENAME main" +if [ -n "${GITHUB_ENV:-}" ]; then + printf 'CC=%s\nGCOV=%s\n' "$cc" "$gcov" >> "$GITHUB_ENV" + printf '%s\n' "$venv_dir/bin" >> "$GITHUB_PATH" +fi -python3 -m pip install --upgrade pip -pip3 install gcovr==4.1 -pip install --upgrade cmake \ No newline at end of file +"$venv_dir/bin/gcovr" --version | head -1 +"$cc" --version | head -1 +"$gcov" --version | head -1 diff --git a/scripts/prepare_release.sh b/scripts/prepare_release.sh index 14b7556..93fa282 100755 --- a/scripts/prepare_release.sh +++ b/scripts/prepare_release.sh @@ -14,6 +14,7 @@ # # Copyright (C) 2026 Eclipse ThreadX contributors # SPDX-License-Identifier: MIT +# Portions of this file were generated with AI assistance. set -eu @@ -117,8 +118,6 @@ sed -i -E "s|(#define LEVELX_HOTFIX_VERSION[[:space:]]+)'[^']*'|\1${HOTFIX_DEFIN git -C "${REPO_ROOT}" add "${API_HEADER}" git -C "${REPO_ROOT}" commit -F - <<'COMMIT_EOF' Updated version number constants - -Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> COMMIT_EOF printf "Committed version constant updates.\n" diff --git a/scripts/test.sh b/scripts/test.sh index 1c4be22..763b793 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -10,4 +10,10 @@ # SPDX-License-Identifier: MIT ############################################################################## -$(dirname `realpath $0`)/../test/cmake/run.sh test all \ No newline at end of file +set -euo pipefail + +test_dir="$(dirname "$(realpath "$0")")/../test/cmake" +"$test_dir/run.sh" test all +if [ "${TX_COVERAGE:-OFF}" = ON ]; then + "$test_dir/check_coverage.sh" +fi diff --git a/test/cmake/CMakeLists.txt b/test/cmake/CMakeLists.txt index fbcd573..eb35819 100644 --- a/test/cmake/CMakeLists.txt +++ b/test/cmake/CMakeLists.txt @@ -78,14 +78,19 @@ add_subdirectory(regression) add_subdirectory(samples) # Coverage -if(CMAKE_BUILD_TYPE MATCHES ".*_coverage") +option(TX_COVERAGE "Instrument every regression configuration" OFF) +if(TX_COVERAGE OR CMAKE_BUILD_TYPE MATCHES ".*_coverage") target_compile_options(levelx PRIVATE -fprofile-arcs -ftest-coverage) target_link_options(levelx PRIVATE -fprofile-arcs -ftest-coverage) endif() # Build ThreadX library once -execute_process(COMMAND ${CMAKE_CURRENT_LIST_DIR}/run.sh build_libs) +execute_process(COMMAND ${CMAKE_CURRENT_LIST_DIR}/run.sh build_libs + RESULT_VARIABLE dependency_status) +if(NOT dependency_status EQUAL 0) + message(FATAL_ERROR "Dependency build failed: ${dependency_status}") +endif() add_custom_target(build_libs ALL COMMAND ${CMAKE_CURRENT_LIST_DIR}/run.sh build_libs) add_dependencies(levelx build_libs) diff --git a/test/cmake/README.md b/test/cmake/README.md new file mode 100644 index 0000000..3b204a8 --- /dev/null +++ b/test/cmake/README.md @@ -0,0 +1,62 @@ +# Linux regression tests + +Use GCC 14, matching gcov 14, CMake, Ninja and gcovr 8.6. The CI installer +supports Ubuntu 24.04. From the repository root: + +```sh +export CC=gcc-14 GCOV=gcov-14 TX_COVERAGE=ON +./scripts/build.sh +./scripts/test.sh +``` + +The runner verifies the dependency commits recorded in `threadx-revision.txt` +and `filex-revision.txt`. The FileX revision is the tested head of +[eclipse-threadx/filex#106](https://github.com/eclipse-threadx/filex/pull/106), +which is still open. Updating a dependency requires changing its pin and +rerunning all ten configurations. An existing checkout with another revision +or modified tracked files is rejected. + +All configurations need the FileX source tree. Non-standalone configurations +link the shared ThreadX and FileX libraries; standalone configurations build +FileX with standalone support. LevelX has no Windows regression port. + +`TX_COVERAGE=ON` instruments every LevelX configuration. A complete test run +clears old coverage first, runs every configuration, and collects coverage even +when a test fails. Reports under `coverage_report/per_configuration` contain +JSON, XML and HTML for each configuration. The `merged` reports combine all ten +JSON inputs and use repository-relative source names. Missing, empty or +unmeasured inputs fail collection. Both line and branch floors are enforced by +`coverage.sh`; the workflow also enforces its integer line floor. + +After a successful full run, `./test/cmake/check_coverage.sh` verifies rejection +of missing, empty and unmeasured coverage inputs, then restores and remerges the +valid reports. + +The reusable workflow pin supplies bounded install, build and test steps and +retains test and coverage artifacts on failure. Only master push or manual +runs can deploy coverage. Dependabot updates target dev; GitHub activates this +configuration once it is present on the default branch. + +## Coverage target + +The measured GCC 14 union is 1,632/2,400 lines (68.00%) and 1,596/2,439 +branches (65.44%). The enforced floors are 68.0% lines and 65.4% branches. +The 100% target still needs 768 lines and 843 branches covered. The largest +line gaps are the FileX simulator adapters (164), NOR block reclaim (69), NOR +extended open (65), and NAND block data movement (38). Other gaps include +media-error paths, metadata allocation, sector release and simulator failures. +These sources remain in the denominator. + +The ECC regression checks every single-bit position in a 512-byte page, +corrections in both halves, and uncorrectable errors. All four ECC helper files +have full line coverage. The full, driver-interface and combined NOR cache +configurations contribute 97, 51 and 65 source lines absent from the default +configuration respectively. Standalone configurations select a subset of the +same source lines; their reports still participate in the union. Function +merging uses the earliest declaration line because driver-interface macros +place otherwise identical function declarations on different lines. + +The ECC test views aligned `USHORT` storage through a character pointer because +the ECC implementation accesses words. This is a deviation from advisory +MISRA C:2004 Rule 11.4; character access preserves alignment and is explicitly +permitted by the character-pointer exception in MISRA C:2012/2023 Rule 11.3. diff --git a/test/cmake/check_coverage.sh b/test/cmake/check_coverage.sh new file mode 100755 index 0000000..4bdc07c --- /dev/null +++ b/test/cmake/check_coverage.sh @@ -0,0 +1,40 @@ +#!/bin/bash +# Copyright (c) 2026 Eclipse ThreadX contributors +# SPDX-License-Identifier: MIT + +set -euo pipefail +cd "$(dirname "$0")" + +# Verify rejection of damaged inputs using a completed coverage run. +base=coverage_report/per_configuration/default_build_coverage +backup=$(mktemp -d) +cp "$base.json" "$backup/input.json" +cp "$base.xml" "$backup/input.xml" +# Restore the original inputs on success or failure. +restore() { + cp "$backup/input.json" "$base.json" + cp "$backup/input.xml" "$base.xml" + rm -r "$backup" +} +trap restore EXIT + +# Require the collector to reject each damaged input. +expect_failure() { + if ./coverage.sh --merge > "$backup/output.log" 2>&1; then + echo "Coverage accepted $1." >&2 + exit 1 + fi + echo "Rejected $1." +} + +rm "$base.json" +expect_failure 'a missing JSON input' +: > "$base.json" +expect_failure 'an empty JSON input' +printf '{"files":[]}\n' > "$base.json" +expect_failure 'an unmeasured JSON input' +cp "$backup/input.json" "$base.json" +printf '\n' > "$base.xml" +expect_failure 'an unmeasured XML input' +cp "$backup/input.xml" "$base.xml" +./coverage.sh --merge diff --git a/test/cmake/coverage.sh b/test/cmake/coverage.sh index 817b62f..cf9468a 100755 --- a/test/cmake/coverage.sh +++ b/test/cmake/coverage.sh @@ -10,10 +10,144 @@ # SPDX-License-Identifier: MIT ############################################################################## +set -euo pipefail -set -e +cd "$(dirname "$0")" +repo_root=$(cd ../.. && pwd) +report_dir=coverage_report +configurations=( + default_build_coverage free_sector_verify_build full_build standalone_build + standalone_free_sector_verify_build standalone_full_build + new_driver_interface_build nor_obsolete_cache_build nor_mapping_cache_build + nor_obsolete_mapping_cache_build +) -cd $(dirname $0) -mkdir -p coverage_report/$1 -gcovr --object-directory=build/$1/levelx/CMakeFiles/levelx.dir/common/src -r ../../common/src --xml-pretty --output coverage_report/$1.xml -gcovr --object-directory=build/$1/levelx/CMakeFiles/levelx.dir/common/src -r ../../common/src --html --html-details --output coverage_report/$1/index.html +# Validate measured sources and make the XML source root portable. +check_report() { + python3 - "$@" <<'PY' +import json +import pathlib +import sys +import xml.etree.ElementTree as ET + +report = pathlib.Path(sys.argv[1]) +tree = ET.parse(report) +root = tree.getroot() +for source in root.findall('./sources/source'): + source.text = '.' +tree.write(report, encoding='utf-8', xml_declaration=True) +classes = root.findall('.//class') +if int(root.get('lines-valid', '0')) == 0 or not classes: + raise SystemExit(f'{report}: report contains no measured LevelX files') +if any(not item.get('filename', '').startswith('common/src/') for item in classes): + raise SystemExit(f'{report}: report contains a source outside common/src') +if len(sys.argv) > 2: + with open(sys.argv[2], encoding='utf-8') as stream: + data = json.load(stream) + if not data.get('files') or not any(item.get('lines') for item in data['files']): + raise SystemExit(f'{sys.argv[2]}: tracefile contains no measured files') + if any(not item.get('file', '').startswith('common/src/') for item in data['files']): + raise SystemExit(f'{sys.argv[2]}: tracefile contains a source outside common/src') +print(f"{report}: lines {root.get('lines-covered')}/{root.get('lines-valid')} " + f"({float(root.get('line-rate', '0')) * 100:.2f}%), branches " + f"{root.get('branches-covered')}/{root.get('branches-valid')} " + f"({float(root.get('branch-rate', '0')) * 100:.2f}%)") +PY +} + +# Enforce separate line and branch coverage floors on the union. +check_merged_floor() { + python3 - "$1" <<'PY' +import sys +import xml.etree.ElementTree as ET + +root = ET.parse(sys.argv[1]).getroot() +for label, attribute, minimum in ( + ('line', 'lines', 680), + ('branch', 'branches', 654), +): + covered = int(root.get(f'{attribute}-covered', '0')) + valid = int(root.get(f'{attribute}-valid', '0')) + if valid == 0 or covered * 1000 < valid * minimum: + raise SystemExit( + f'{sys.argv[1]}: {label} coverage {covered}/{valid} ' + f'is below {minimum / 10:.1f}%' + ) +PY +} + +if [ "${1:-}" = --clean ]; then + if [ -d "$report_dir" ]; then + rm -r -- "$report_dir" + fi + if [ -d build ]; then + find build -type f -name '*.gcda' -delete + fi + exit 0 +fi + +if [ "${1:-}" = --merge ]; then + trace_args=() + for configuration in "${configurations[@]}"; do + base="$report_dir/per_configuration/$configuration" + for path in "$base.json" "$base.xml" "$base/index.html"; do + if [ ! -s "$path" ]; then + echo "Missing or empty coverage report: $path" >&2 + exit 1 + fi + done + check_report "$base.xml" "$base.json" + trace_args+=(--add-tracefile "$base.json") + done + + # Driver-interface macros place the same function on different source lines. + trace_args+=(--merge-mode-functions=merge-use-line-min) + mkdir -p "$report_dir/merged" + gcovr -r "$repo_root" "${trace_args[@]}" --json "$report_dir/merged.json" --xml-pretty \ + --output "$report_dir/merged.xml" + gcovr -r "$repo_root" "${trace_args[@]}" --html --html-details \ + --output "$report_dir/merged/index.html" + check_report "$report_dir/merged.xml" "$report_dir/merged.json" + check_merged_floor "$report_dir/merged.xml" + exit 0 +fi + +configuration="${1:-}" +valid=0 +for item in "${configurations[@]}"; do + if [ "$configuration" = "$item" ]; then + valid=1 + break + fi +done +if [ "$valid" -ne 1 ]; then + echo "Unknown coverage configuration: $configuration" >&2 + exit 1 +fi + +cc_name=$(basename "${CC:-gcc}") +if [ -n "${GCOV:-}" ]; then + gcov="$GCOV" +elif [[ "$cc_name" = gcc* ]]; then + gcov="gcov${cc_name#gcc}" +else + gcov=gcov +fi +if ! command -v "$gcov" >/dev/null 2>&1; then + echo "Coverage tool $gcov is unavailable." >&2 + exit 1 +fi + +objects="$PWD/build/$configuration/levelx/CMakeFiles/levelx.dir/common/src" +if [ ! -d "$objects" ] || [ -z "$(find "$objects" -name '*.gcda' -print -quit)" ]; then + echo "No LevelX coverage data for $configuration." >&2 + exit 1 +fi + +base="$report_dir/per_configuration/$configuration" +mkdir -p "$base" +gcovr --gcov-executable "$gcov" -r "$repo_root" -f "$repo_root/common/src" \ + "$objects" --json "$base.json" --xml-pretty --output "$base.xml" +gcovr --gcov-executable "$gcov" -r "$repo_root" -f "$repo_root/common/src" \ + "$objects" --html --html-details --output "$base/index.html" +check_report "$base.xml" "$base.json" diff --git a/test/cmake/filex-revision.txt b/test/cmake/filex-revision.txt new file mode 100644 index 0000000..6a821fd --- /dev/null +++ b/test/cmake/filex-revision.txt @@ -0,0 +1 @@ +79c703d917e648c615ae6e0c253e63a73bb134a5 diff --git a/test/cmake/regression/CMakeLists.txt b/test/cmake/regression/CMakeLists.txt index f92c71a..10bf3d8 100644 --- a/test/cmake/regression/CMakeLists.txt +++ b/test/cmake/regression/CMakeLists.txt @@ -6,6 +6,7 @@ project(regression_test LANGUAGES C) set(SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/../../regression) set(regression_test_cases + ${SOURCE_DIR}/levelx_nand_ecc_test.c ${SOURCE_DIR}/levelx_nand_flash_test.c ${SOURCE_DIR}/levelx_nor_flash_test.c ${SOURCE_DIR}/levelx_nor_flash_test_cache.c) @@ -18,3 +19,9 @@ foreach(test_case ${regression_test_cases} ${regression_test_cases_exfat}) target_compile_definitions(${test_name} PRIVATE BATCH_TEST) add_test(${CMAKE_BUILD_TYPE}::${test_name} ${test_name}) endforeach() + +add_executable(levelx_nor_reclaim_failure_test + ${SOURCE_DIR}/levelx_nor_reclaim_failure_test.c) +target_link_libraries(levelx_nor_reclaim_failure_test PRIVATE azrtos::levelx) +add_test(${CMAKE_BUILD_TYPE}::levelx_nor_reclaim_failure_test + levelx_nor_reclaim_failure_test) diff --git a/test/cmake/run.sh b/test/cmake/run.sh index 3f8159d..4c50615 100755 --- a/test/cmake/run.sh +++ b/test/cmake/run.sh @@ -10,11 +10,34 @@ # SPDX-License-Identifier: MIT ############################################################################## +set -euo pipefail -cd $(dirname $0) +cd "$(dirname "$0")" +for dependency in threadx filex; do + revision=$(cat "$dependency-revision.txt") + repository="https://github.com/eclipse-threadx/$dependency.git" + if [ ! -e "$dependency" ]; then + git init -q "$dependency" + git -C "$dependency" remote add origin "$repository" + timeout 180 git -C "$dependency" fetch --depth 1 origin "$revision" + git -C "$dependency" checkout -q --detach FETCH_HEAD + fi + if [ "$(git -C "$dependency" rev-parse HEAD)" != "$revision" ] || + [ -n "$(git -C "$dependency" status --porcelain --untracked-files=no)" ]; then + echo "$dependency checkout does not match its pinned revision." >&2 + exit 1 + fi +done -# if threadx repo does not exist, clone it -[ -d threadx ] || git clone https://github.com/eclipse-threadx/threadx.git --depth 1 -[ -d filex ] || git clone https://github.com/eclipse-threadx/filex.git --depth 1 -[ -f .run.sh ] || ln -sf threadx/scripts/cmake_bootstrap.sh .run.sh -./.run.sh $* \ No newline at end of file +bootstrap=threadx/scripts/cmake_bootstrap.sh +if [ ! -f "$bootstrap" ]; then + echo "ThreadX bootstrap script is missing." >&2 + exit 1 +fi + +if [ "${1:-}" = test ] && [ "${2:-}" = all ] && [ "${TX_COVERAGE:-OFF}" = ON ]; then + ./coverage.sh --clean +fi + +ln -sfn "$bootstrap" .run.sh +exec ./.run.sh "$@" diff --git a/test/cmake/threadx-revision.txt b/test/cmake/threadx-revision.txt new file mode 100644 index 0000000..b01234d --- /dev/null +++ b/test/cmake/threadx-revision.txt @@ -0,0 +1 @@ +b37cd4a81a1cb8c2ebefc438220ab7f009e13362 diff --git a/test/regression/levelx_nand_ecc_test.c b/test/regression/levelx_nand_ecc_test.c new file mode 100644 index 0000000..2831bcd --- /dev/null +++ b/test/regression/levelx_nand_ecc_test.c @@ -0,0 +1,100 @@ +/*************************************************************************** + * Copyright (c) 2026 Eclipse ThreadX contributors + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * AI Disclosure: This file was largely AI-generated by Codex (GPT-6). + * The AI-generated portions may be considered public domain (CC0-1.0) + * and not subject to the project's licence. The human contributor has + * reviewed and verified that the code is correct. + * + * SPDX-License-Identifier: MIT and CC0-1.0 + **************************************************************************/ + +#include +#include "lx_api.h" + +#ifndef LX_STANDALONE_ENABLE +/* Supply the application hook required by the shared ThreadX library. */ +VOID tx_application_define(VOID *first_unused_memory) +{ + (void) first_unused_memory; +} +#endif + +/* Check page ECC correction across both 256-byte portions of a page. */ +int main(void) +{ + +LX_NAND_FLASH flash = {0}; +USHORT storage[256]; +UCHAR *page = (UCHAR *) storage; +UCHAR expected[512]; +UCHAR ecc[6]; +UINT i; +UINT bit; +int failed = 0; + + flash.lx_nand_flash_bytes_per_page = 512; + for (i = 0; i < 512; i++) + { + expected[i] = (UCHAR) (i & 0xFFU); + } + memcpy(page, expected, sizeof(expected)); + if (lx_nand_flash_page_ecc_compute(&flash, page, ecc) != LX_SUCCESS) + { + failed = 1; + } + if (lx_nand_flash_page_ecc_check(&flash, page, ecc) != LX_SUCCESS) + { + failed = 1; + } + + /* Every single data bit must be corrected without changing other bytes. */ + for (i = 0; i < 512; i++) + { + for (bit = 0; bit < 8; bit++) + { + memcpy(page, expected, sizeof(expected)); + page[i] ^= (UCHAR) (1U << bit); + if (lx_nand_flash_page_ecc_check(&flash, page, ecc) != LX_NAND_ERROR_CORRECTED) + { + failed = 1; + } + if (memcmp(page, expected, sizeof(expected)) != 0) + { + failed = 1; + } + } + } + + /* Corrections in both portions must retain the corrected status. */ + page[0] ^= 1U; + page[256] ^= 1U; + if (lx_nand_flash_page_ecc_check(&flash, page, ecc) != LX_NAND_ERROR_CORRECTED) + { + failed = 1; + } + if (memcmp(page, expected, sizeof(expected)) != 0) + { + failed = 1; + } + + /* An uncorrectable second portion must override a first-portion correction. */ + page[0] ^= 1U; + page[256] ^= 3U; + if (lx_nand_flash_page_ecc_check(&flash, page, ecc) != LX_NAND_ERROR_NOT_CORRECTED) + { + failed = 1; + } + memcpy(page, expected, sizeof(expected)); + page[0] ^= 3U; + if (lx_nand_flash_page_ecc_check(&flash, page, ecc) != LX_NAND_ERROR_NOT_CORRECTED) + { + failed = 1; + } + + return(failed); +} diff --git a/test/regression/levelx_nor_flash_test_cache.c b/test/regression/levelx_nor_flash_test_cache.c index d5f595b..fb15ec5 100644 --- a/test/regression/levelx_nor_flash_test_cache.c +++ b/test/regression/levelx_nor_flash_test_cache.c @@ -9,7 +9,7 @@ /* SPDX-License-Identifier: MIT */ /***************************************************************************/ -/* Portions of this file were generated with AI assistance. */ +// Portions of this file were generated with AI assistance. /* Basic NOR flash tests... */ @@ -33,11 +33,31 @@ ULONG readbuffer[128]; UCHAR nor_cache_memory[2048+16+8]; UCHAR nor_cache_memory2[8192]; +UCHAR nor_cache_memory_invalid[256]; +#ifndef LX_NOR_DISABLE_EXTENDED_CACHE +UCHAR nor_zero_base_cache_memory[512]; +ULONG nor_zero_base_memory[LX_NOR_SECTOR_SIZE * 2]; +ULONG nor_zero_base_driver_read_count; +#endif /* Define LevelX NOR flash simulator prototoypes. */ UINT _lx_nor_flash_simulator_initialize(LX_NOR_FLASH *nor_flash); +#ifndef LX_NOR_DISABLE_EXTENDED_CACHE +#ifdef LX_NOR_ENABLE_CONTROL_BLOCK_FOR_DRIVER_INTERFACE +static UINT zero_base_nor_read(LX_NOR_FLASH *nor_flash, ULONG *flash_address, ULONG *destination, ULONG words); +static UINT zero_base_nor_write(LX_NOR_FLASH *nor_flash, ULONG *flash_address, ULONG *source, ULONG words); +static UINT zero_base_nor_block_erase(LX_NOR_FLASH *nor_flash, ULONG block, ULONG erase_count); +static UINT zero_base_nor_block_erased_verify(LX_NOR_FLASH *nor_flash, ULONG block); +#else +static UINT zero_base_nor_read(ULONG *flash_address, ULONG *destination, ULONG words); +static UINT zero_base_nor_write(ULONG *flash_address, ULONG *source, ULONG words); +static UINT zero_base_nor_block_erase(ULONG block, ULONG erase_count); +static UINT zero_base_nor_block_erased_verify(ULONG block); +#endif +static UINT zero_base_nor_initialize(LX_NOR_FLASH *nor_flash); +#endif @@ -75,12 +95,177 @@ void tx_application_define(void *first_unused_memory) } #endif +#ifndef LX_NOR_DISABLE_EXTENDED_CACHE +static UINT zero_base_nor_initialize(LX_NOR_FLASH *nor_flash) +{ + +UINT status; + + + /* Setup the base address as a logical zero offset. */ + nor_flash -> lx_nor_flash_base_address = LX_NULL; + + /* Setup geometry of the test flash. */ + nor_flash -> lx_nor_flash_total_blocks = 1; + nor_flash -> lx_nor_flash_words_per_block = LX_NOR_SECTOR_SIZE * 2; + + /* Setup function pointers for the NOR flash services. */ + nor_flash -> lx_nor_flash_driver_read = zero_base_nor_read; + nor_flash -> lx_nor_flash_driver_write = zero_base_nor_write; + nor_flash -> lx_nor_flash_driver_block_erase = zero_base_nor_block_erase; + nor_flash -> lx_nor_flash_driver_block_erased_verify = zero_base_nor_block_erased_verify; + + /* Setup local buffer for NOR flash operation. */ + nor_flash -> lx_nor_flash_sector_buffer = buffer; + + /* Erase the test flash. */ +#ifdef LX_NOR_ENABLE_CONTROL_BLOCK_FOR_DRIVER_INTERFACE + status = zero_base_nor_block_erase(nor_flash, 0, 0); +#else + status = zero_base_nor_block_erase(0, 0); +#endif + + /* Return completion status. */ + return(status); +} + +#ifdef LX_NOR_ENABLE_CONTROL_BLOCK_FOR_DRIVER_INTERFACE +static UINT zero_base_nor_read(LX_NOR_FLASH *nor_flash, ULONG *flash_address, ULONG *destination, ULONG words) +#else +static UINT zero_base_nor_read(ULONG *flash_address, ULONG *destination, ULONG words) +#endif +{ + +ULONG offset; + +#ifdef LX_NOR_ENABLE_CONTROL_BLOCK_FOR_DRIVER_INTERFACE + LX_PARAMETER_NOT_USED(nor_flash); +#endif + + /* MISRA C:2012 Rule 11.4 deviation: this test driver intentionally treats + NOR flash addresses as logical offsets, including zero. */ + offset = ((ULONG)flash_address) / sizeof(ULONG); + + if ((offset + words) > (LX_NOR_SECTOR_SIZE * 2)) + { + return(LX_ERROR); + } + + nor_zero_base_driver_read_count++; + + while (words--) + { + *destination++ = nor_zero_base_memory[offset++]; + } + + return(LX_SUCCESS); +} + +#ifdef LX_NOR_ENABLE_CONTROL_BLOCK_FOR_DRIVER_INTERFACE +static UINT zero_base_nor_write(LX_NOR_FLASH *nor_flash, ULONG *flash_address, ULONG *source, ULONG words) +#else +static UINT zero_base_nor_write(ULONG *flash_address, ULONG *source, ULONG words) +#endif +{ + +ULONG offset; + +#ifdef LX_NOR_ENABLE_CONTROL_BLOCK_FOR_DRIVER_INTERFACE + LX_PARAMETER_NOT_USED(nor_flash); +#endif + + /* MISRA C:2012 Rule 11.4 deviation: this test driver intentionally treats + NOR flash addresses as logical offsets, including zero. */ + offset = ((ULONG)flash_address) / sizeof(ULONG); + + if ((offset + words) > (LX_NOR_SECTOR_SIZE * 2)) + { + return(LX_ERROR); + } + + while (words--) + { + nor_zero_base_memory[offset++] = *source++; + } + + return(LX_SUCCESS); +} + +#ifdef LX_NOR_ENABLE_CONTROL_BLOCK_FOR_DRIVER_INTERFACE +static UINT zero_base_nor_block_erase(LX_NOR_FLASH *nor_flash, ULONG block, ULONG erase_count) +#else +static UINT zero_base_nor_block_erase(ULONG block, ULONG erase_count) +#endif +{ + +ULONG offset; +ULONG words; + +#ifdef LX_NOR_ENABLE_CONTROL_BLOCK_FOR_DRIVER_INTERFACE + LX_PARAMETER_NOT_USED(nor_flash); +#endif + LX_PARAMETER_NOT_USED(erase_count); + + if (block != 0) + { + return(LX_ERROR); + } + + offset = 0; + words = LX_NOR_SECTOR_SIZE * 2; + + while (words--) + { + nor_zero_base_memory[offset++] = LX_ALL_ONES; + } + + return(LX_SUCCESS); +} + +#ifdef LX_NOR_ENABLE_CONTROL_BLOCK_FOR_DRIVER_INTERFACE +static UINT zero_base_nor_block_erased_verify(LX_NOR_FLASH *nor_flash, ULONG block) +#else +static UINT zero_base_nor_block_erased_verify(ULONG block) +#endif +{ + +ULONG offset; +ULONG words; + +#ifdef LX_NOR_ENABLE_CONTROL_BLOCK_FOR_DRIVER_INTERFACE + LX_PARAMETER_NOT_USED(nor_flash); +#endif + + if (block != 0) + { + return(LX_ERROR); + } + + offset = 0; + words = LX_NOR_SECTOR_SIZE * 2; + + while (words--) + { + if (nor_zero_base_memory[offset++] != LX_ALL_ONES) + { + return(LX_ERROR); + } + } + + return(LX_SUCCESS); +} +#endif + /* Define the test threads. */ void thread_0_entry(ULONG thread_input) { ULONG i, j, sector; +#ifndef LX_NOR_DISABLE_EXTENDED_CACHE +ULONG read_count; +ULONG *last_sector_address; +#endif UINT status; ULONG *word_ptr; @@ -95,6 +280,148 @@ ULONG obsolete_sectors = LX_ALL_ONES; /* Initialize LevelX. */ _lx_nor_flash_initialize(); +#ifndef LX_NOR_DISABLE_EXTENDED_CACHE + /* Test 0: Extended cache with a zero NOR flash base address. */ + printf("Test 0: Extended cache with zero base address...."); + + LX_MEMSET(&nor_sim_flash, 0, sizeof(nor_sim_flash)); + status = zero_base_nor_initialize(&nor_sim_flash); + nor_zero_base_memory[0] = 0x12345678; + /* MISRA C:2012 Rule 11.6 deviation: this test driver intentionally + treats NOR flash addresses as logical offset tokens. */ + last_sector_address = (ULONG *)(LX_NOR_SECTOR_SIZE * sizeof(ULONG)); + nor_zero_base_driver_read_count = 0; + + if (status == LX_SUCCESS) + { + status = lx_nor_flash_extended_cache_enable(&nor_sim_flash, nor_zero_base_cache_memory, sizeof(nor_zero_base_cache_memory)); + } + + if (status == LX_SUCCESS) + { + status = _lx_nor_flash_driver_read(&nor_sim_flash, LX_NULL, readbuffer, 1); + } + + if ((status != LX_SUCCESS) || (readbuffer[0] != 0x12345678) || (nor_zero_base_driver_read_count != 1)) + { + printf("FAILED!\n"); +#ifdef BATCH_TEST + exit(1); +#endif + while(1) + { + } + } + + status = _lx_nor_flash_driver_read(&nor_sim_flash, LX_NULL, readbuffer, 1); + + if ((status != LX_SUCCESS) || (readbuffer[0] != 0x12345678) || (nor_zero_base_driver_read_count != 1)) + { + printf("FAILED!\n"); +#ifdef BATCH_TEST + exit(1); +#endif + while(1) + { + } + } + + buffer[0] = 0x87654321; + status = _lx_nor_flash_driver_write(&nor_sim_flash, LX_NULL, buffer, 1); + + if ((status != LX_SUCCESS) || (nor_zero_base_memory[0] != 0x87654321)) + { + printf("FAILED!\n"); +#ifdef BATCH_TEST + exit(1); +#endif + while(1) + { + } + } + + status = _lx_nor_flash_driver_read(&nor_sim_flash, LX_NULL, readbuffer, 1); + + if ((status != LX_SUCCESS) || (readbuffer[0] != 0x87654321) || (nor_zero_base_driver_read_count != 1)) + { + printf("FAILED!\n"); +#ifdef BATCH_TEST + exit(1); +#endif + while(1) + { + } + } + + read_count = nor_zero_base_driver_read_count; + status = _lx_nor_flash_driver_block_erase(&nor_sim_flash, 0, 0); + + if (status == LX_SUCCESS) + { + status = _lx_nor_flash_driver_read(&nor_sim_flash, LX_NULL, readbuffer, 1); + } + + if ((status != LX_SUCCESS) || (readbuffer[0] != LX_ALL_ONES) || (nor_zero_base_driver_read_count != (read_count + 1))) + { + printf("FAILED!\n"); +#ifdef BATCH_TEST + exit(1); +#endif + while(1) + { + } + } + + nor_zero_base_memory[LX_NOR_SECTOR_SIZE] = 0xABCDEF01; + + status = _lx_nor_flash_driver_read(&nor_sim_flash, last_sector_address, readbuffer, 1); + + if ((status != LX_SUCCESS) || (readbuffer[0] != 0xABCDEF01) || (nor_zero_base_driver_read_count != (read_count + 2))) + { + printf("FAILED!\n"); +#ifdef BATCH_TEST + exit(1); +#endif + while(1) + { + } + } + + status = _lx_nor_flash_driver_read(&nor_sim_flash, last_sector_address, readbuffer, 1); + + if ((status != LX_SUCCESS) || (readbuffer[0] != 0xABCDEF01) || (nor_zero_base_driver_read_count != (read_count + 2))) + { + printf("FAILED!\n"); +#ifdef BATCH_TEST + exit(1); +#endif + while(1) + { + } + } + + read_count = nor_zero_base_driver_read_count; + status = _lx_nor_flash_driver_block_erase(&nor_sim_flash, 0, 0); + + if (status == LX_SUCCESS) + { + status = _lx_nor_flash_driver_read(&nor_sim_flash, last_sector_address, readbuffer, 1); + } + + if ((status != LX_SUCCESS) || (readbuffer[0] != LX_ALL_ONES) || (nor_zero_base_driver_read_count != (read_count + 1))) + { + printf("FAILED!\n"); +#ifdef BATCH_TEST + exit(1); +#endif + while(1) + { + } + } + + printf("SUCCESS!\n"); +#endif + /* Test 1: Simple write 100 sectors and read 100 sectors. */ printf("Test 1: Simple write-read 100 sectors..........."); @@ -1600,7 +1927,25 @@ status += lx_nor_flash_extended_cache_enable(&nor_sim_flash, nor_cache_memory, s /* Open the flash. */ status = lx_nor_flash_open(&nor_sim_flash, "sim nor flash", _lx_nor_flash_simulator_initialize); #ifndef LX_NOR_DISABLE_EXTENDED_CACHE -status += lx_nor_flash_extended_cache_enable(&nor_sim_flash, nor_cache_memory2, sizeof(nor_cache_memory2)); + if (status == LX_SUCCESS) + { + + status = lx_nor_flash_extended_cache_enable(&nor_sim_flash, nor_cache_memory_invalid, sizeof(nor_cache_memory_invalid)); + + if (status != LX_ERROR) + { + + printf("FAILED!\n"); +#ifdef BATCH_TEST + exit(1); +#endif + while(1) + { + } + } + + status = lx_nor_flash_extended_cache_enable(&nor_sim_flash, nor_cache_memory2, sizeof(nor_cache_memory2)); + } #endif if (status != LX_SUCCESS) @@ -1706,5 +2051,3 @@ status += lx_nor_flash_extended_cache_enable(&nor_sim_flash, nor_cache_memory2, { } } - - diff --git a/test/regression/levelx_nor_reclaim_failure_test.c b/test/regression/levelx_nor_reclaim_failure_test.c new file mode 100644 index 0000000..035d95a --- /dev/null +++ b/test/regression/levelx_nor_reclaim_failure_test.c @@ -0,0 +1,160 @@ +/*************************************************************************** + * Copyright (c) 2026 Eclipse ThreadX contributors + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * AI Disclosure: This file was largely AI-generated by Codex (GPT-6). + * The AI-generated portions may be considered public domain (CC0-1.0) + * and not subject to the project's licence. The human contributor has + * reviewed and verified that the code is correct. + * + * SPDX-License-Identifier: MIT and CC0-1.0 + **************************************************************************/ + +#include +#include "lx_api.h" + +static LX_NOR_FLASH nor_flash; +static ULONG flash_words[256]; +static UINT allocation_calls; +static UINT invalidation_calls; +static UINT unexpected_driver_calls; +static UINT fail_on_retry; + + +#ifndef LX_STANDALONE_ENABLE +/* Provide the ThreadX application entry point required by the host library. */ +VOID tx_application_define(VOID *first_unused_memory) +{ + LX_PARAMETER_NOT_USED(first_unused_memory); +} +#endif + + +/* Select a block with one mapped sector for reclamation. */ +UINT _lx_nor_flash_next_block_to_erase_find(LX_NOR_FLASH *flash, ULONG *erase_block, + ULONG *erase_count, ULONG *mapped_sectors, + ULONG *obsolete_sectors) +{ + LX_PARAMETER_NOT_USED(flash); + *erase_block = 0; + *erase_count = 0; + *mapped_sectors = 1; + *obsolete_sectors = 0; + return(LX_SUCCESS); +} + + +/* Fail either the first allocation or the retry after selecting the erase block. */ +UINT _lx_nor_flash_physical_sector_allocate(LX_NOR_FLASH *flash, ULONG logical_sector, + ULONG **mapping_address, ULONG **sector_address) +{ + LX_PARAMETER_NOT_USED(flash); + LX_PARAMETER_NOT_USED(logical_sector); + allocation_calls++; + + if ((fail_on_retry != 0U) && (allocation_calls == 1U)) + { + *mapping_address = &flash_words[2]; + *sector_address = &flash_words[8]; + return(LX_SUCCESS); + } + + *mapping_address = LX_NULL; + *sector_address = LX_NULL; + return(LX_NO_SECTORS); +} + + +/* Count mapping-cache invalidations before allocation. */ +VOID _lx_nor_flash_sector_mapping_cache_invalidate(LX_NOR_FLASH *flash, ULONG logical_sector) +{ + LX_PARAMETER_NOT_USED(flash); + LX_PARAMETER_NOT_USED(logical_sector); + invalidation_calls++; +} + + +/* Supply mapped-list words when direct reading is disabled. */ +UINT _lx_nor_flash_driver_read(LX_NOR_FLASH *flash, ULONG *address, ULONG *destination, ULONG words) +{ + ULONG i; + + LX_PARAMETER_NOT_USED(flash); + for (i = 0; i < words; i++) + { + destination[i] = address[i]; + } + return(LX_SUCCESS); +} + + +/* Record writes that must not occur after allocation failure. */ +UINT _lx_nor_flash_driver_write(LX_NOR_FLASH *flash, ULONG *address, ULONG *source, ULONG words) +{ + LX_PARAMETER_NOT_USED(flash); + LX_PARAMETER_NOT_USED(address); + LX_PARAMETER_NOT_USED(source); + LX_PARAMETER_NOT_USED(words); + unexpected_driver_calls++; + return(LX_SUCCESS); +} + + +/* Record erases that must not occur after allocation failure. */ +UINT _lx_nor_flash_driver_block_erase(LX_NOR_FLASH *flash, ULONG block, ULONG erase_count) +{ + LX_PARAMETER_NOT_USED(flash); + LX_PARAMETER_NOT_USED(block); + LX_PARAMETER_NOT_USED(erase_count); + unexpected_driver_calls++; + return(LX_SUCCESS); +} + + +/* Check both allocation-failure points in block reclaim. */ +static UINT test_reclaim_allocation_failure(UINT retry) +{ + UINT status; + + (void) memset(&nor_flash, 0, sizeof(nor_flash)); + (void) memset(flash_words, 0, sizeof(flash_words)); + allocation_calls = 0; + invalidation_calls = 0; + unexpected_driver_calls = 0; + fail_on_retry = retry; + + nor_flash.lx_nor_flash_base_address = flash_words; + nor_flash.lx_nor_flash_words_per_block = 128; + nor_flash.lx_nor_flash_total_blocks = 2; + nor_flash.lx_nor_flash_physical_sectors_per_block = 2; + nor_flash.lx_nor_flash_free_physical_sectors = 2; + nor_flash.lx_nor_flash_block_physical_sector_mapping_offset = 1; + flash_words[1] = LX_NOR_PHYSICAL_SECTOR_VALID | 7U; + + status = _lx_nor_flash_block_reclaim(&nor_flash); + if ((status != LX_SYSTEM_ALLOCATION_FAILED) || + (nor_flash.lx_nor_flash_diagnostic_system_error != LX_SYSTEM_ALLOCATION_FAILED) || + (nor_flash.lx_nor_flash_diagnostic_system_errors != 1U) || + (allocation_calls != (retry + 1U)) || (invalidation_calls != 1U) || + (unexpected_driver_calls != 0U)) + { + return(LX_ERROR); + } + return(LX_SUCCESS); +} + + +/* Run failure checks for the first allocation and its retry. */ +int main(void) +{ + if ((test_reclaim_allocation_failure(0U) != LX_SUCCESS) || + (test_reclaim_allocation_failure(1U) != LX_SUCCESS)) + { + return(1); + } + + return(0); +}