From 0203804a9f3464d1093e7314b0a113c44223bf85 Mon Sep 17 00:00:00 2001 From: tyeth Date: Tue, 20 Oct 2026 23:44:48 +0100 Subject: [PATCH 1/3] fwk: epr: make a failed EPR transition recoverable without AC removal During EPR entry/exit the EC intentionally puts the system into a transitional clamp state: charger input current is collapsed to 0mA (learn mode on) and a hard AP throttle is asserted via update_pmf_events(BIT(PD_PROGRESS_ENTER/EXIT_EPR_MODE), 1). On a healthy transition this lasts ~1.1s and both are released after the new contract lands. When the transition fails, two pieces of that transitional state currently persist beyond the transition: 1. Neither failure handler releases the PMF transition event. The normal release path, clear_prochot(), only clears the ENTER event when cypd_get_ac_power() > 100W, which is never true after a failed EPR entry (the standing SPR contract is at most 100W), so the hard throttle outlives the transition it was meant to cover and is left to the 3s force_clear_pmf_prochot backstop at best. Release the ENTER/EXIT transition events at both points where the flow is abandoned: the EPR_MODE_ENTER_FAILED/default branch of cypd_update_epr_state(), and the retry-exhausted branch of epr_flow_pending_deferred() (the path taken when the EPR event from the PD controller never arrives). The failure handler now leaves the system in the plain-SPR state (input limit is already restored by the existing cypd_update_port_state() call on both paths) instead of the mid-transition state. 2. The "EPR fail, do not retry" latch (epr_active = 0xff) is only cleared by clear_port_state() on a PORT_DISCONNECT/hard-reset response. cypd_update_port_state() deliberately preserves it, and a CCG_RESPONSE_PORT_CONNECT does not reset it, so if the disconnect response is missed (the same event-loss class the deferred retry exists for) or EC RAM outlives a PD controller reset, the latch outlives the connection that caused it and the port can never enter EPR again. Clear the latch on PORT_CONNECT: a fresh attach is a new connection, so the do-not-retry intent (no EPR retry on the contract that failed) is preserved while the latch can no longer outlive that connection. Field evidence (FW16 Ryzen AI 300 "tulip", RTX 5070, 240W EPR, EC tulip-4.0.1): instrumented captures show the system latching into the transitional state - charger input stuck at ~0/500-800mA, hard throttle asserted (BIOS runs slow, survives OS reboot), and a port stuck attach-without-contract where EC_CMD_GET_PD_PORT_STATE returns zeros and the EC reports "AC not connected". Resets of either or both PD controllers do not clear it (EC RAM state re-imposes it after every reset); only a physical AC removal recovers, because only the PD_PROGRESS_DISCONNECTED path clears the progress/event state. A healthy reference trace of the same transition (throttle on, input 500mA, ~1.1s, then release) was captured for comparison. Issue: https://github.com/tyeth/framework-system/issues/1 Telemetry/data: https://github.com/tyeth/fw16-gpu-watchdog Related: FrameworkComputer/SoftwareFirmwareIssueTracker #160 #193 #241 #243 Compile-untested: no Zephyr build environment was available; changes were limited to call sites and symbols already used in these files and verified by read-through. Co-Authored-By: Claude Fable 5 --- zephyr/program/framework/src/cypd_ccg8.c | 6 ++++++ zephyr/program/framework/src/cypress_pd_common.c | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/zephyr/program/framework/src/cypd_ccg8.c b/zephyr/program/framework/src/cypd_ccg8.c index 7e3738bf6c..c4026e7780 100644 --- a/zephyr/program/framework/src/cypd_ccg8.c +++ b/zephyr/program/framework/src/cypd_ccg8.c @@ -471,6 +471,9 @@ static void epr_flow_pending_deferred(void) pd_port_states[port_idx].epr_retry_count = 0; pd_port_states[port_idx].epr_support = 0; pd_epr_in_progress &= EPR_PROCESS_MASK; + /* the transition ended, release the transition throttle */ + update_pmf_events(BIT(PD_PROGRESS_ENTER_EPR_MODE), 0); + update_pmf_events(BIT(PD_PROGRESS_EXIT_EPR_MODE), 0); if (charge_port != -1) { int controller = PORT_TO_CONTROLLER(charge_port); int port = PORT_TO_CONTROLLER_PORT(charge_port); @@ -761,6 +764,9 @@ void cypd_update_epr_state(int controller, int port, int response_len) #endif /* EPR fail, do not retry */ pd_port_states[port_idx].epr_active = 0xff; + /* the transition ended, release the transition throttle */ + update_pmf_events(BIT(PD_PROGRESS_ENTER_EPR_MODE), 0); + update_pmf_events(BIT(PD_PROGRESS_EXIT_EPR_MODE), 0); } } diff --git a/zephyr/program/framework/src/cypress_pd_common.c b/zephyr/program/framework/src/cypress_pd_common.c index af4bb255c5..961987a27a 100644 --- a/zephyr/program/framework/src/cypress_pd_common.c +++ b/zephyr/program/framework/src/cypress_pd_common.c @@ -2054,6 +2054,11 @@ void cypd_port_int(int controller, int port) case CCG_RESPONSE_PORT_CONNECT: CPRINTS("CYPD_RESPONSE_PORT_CONNECT %d", port_idx); record_ucsi_connector_change_event(controller, port); +#ifdef CONFIG_PD_CCG8_EPR + /* the EPR fail latch applies to the failed connection only */ + if (pd_port_states[port_idx].epr_active == 0xff) + pd_port_states[port_idx].epr_active = 0; +#endif cypd_update_port_state(controller, port); cypd_evaluate_port_profile(controller, port, data2[0]); break; From a5e8818e17226de35a093bd959b8471d6858668f Mon Sep 17 00:00:00 2001 From: tyeth Date: Tue, 20 Oct 2026 23:56:11 +0100 Subject: [PATCH 2/3] fwk: epr: restore standing contract when the buck transition is abandoned On boards with the RAA489300 3-level buck (tulip), enter_epr_mode() / exit_epr_mode() first collapse the charger input current to 0mA (learn mode on) and assert the PMF transition throttle, then hand off to buck_ready_for_epr() to sequence the buck mode change before the CCG EPR command is sent. If the buck readiness check exhausts its retries, the handler reverts the buck mode and goes idle - but the CCG command is never sent, so no EPR event ever arrives, and because epr_flow_pending_deferred() is only scheduled from handle_pd_epr_mode() (after buck ready), the event-loss backstop never runs either. The pd_epr_in_progress port bits, the collapsed input current and the transition throttle are all left standing: a second independent route into the latched slow state that otherwise only a physical AC removal clears. Give the abandonment branch the same cleanup the retry-exhausted branch of epr_flow_pending_deferred() already performs: clear the in-progress port bits (keeping the process flags so the existing restore block in cypd_update_port_state() runs), release the ENTER/EXIT transition throttle events, and refresh the active charge port so the input current limit is restored from the standing contract. The buck mode revert, retry count and timing are unchanged; on an abandoned exit the standing contract is the still-active EPR contract and epr_active stays 1, so a later exit attempt remains possible. This stacks on "fwk: epr: make a failed EPR transition recoverable without AC removal" (the two compose: that change covers the failure paths after the CCG command is sent, this one covers the flow being abandoned before it is sent). Issue: https://github.com/tyeth/framework-system/issues/1 Telemetry/data: https://github.com/tyeth/fw16-gpu-watchdog Compile-untested: no Zephyr build environment was available; the change uses only symbols already used in this file and was verified by read-through. Co-Authored-By: Claude Fable 5 --- zephyr/program/framework/src/cypd_ccg8.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/zephyr/program/framework/src/cypd_ccg8.c b/zephyr/program/framework/src/cypd_ccg8.c index c4026e7780..f239d41df0 100644 --- a/zephyr/program/framework/src/cypd_ccg8.c +++ b/zephyr/program/framework/src/cypd_ccg8.c @@ -583,6 +583,8 @@ static void buck_ready_for_epr(void) /* Retry mechanism */ if (++epr_buck_ctx.retry_count > 4) { + int charge_port = get_active_charge_pd_port(); + if (is_enter_epr) { CPRINTS("3Level-Buck enter epr failed, reverting to SPR mode"); board_set_buck_mode(LEVEL_BUCK_SPR); @@ -592,6 +594,16 @@ static void buck_ready_for_epr(void) } epr_buck_ctx.retry_count = 0; epr_buck_ctx.phase = BUCK_PHASE_IDLE; + /* the transition ended, restore the standing contract state */ + pd_epr_in_progress &= EPR_PROCESS_MASK; + update_pmf_events(BIT(PD_PROGRESS_ENTER_EPR_MODE), 0); + update_pmf_events(BIT(PD_PROGRESS_EXIT_EPR_MODE), 0); + if (charge_port != -1) { + int controller = PORT_TO_CONTROLLER(charge_port); + int port = PORT_TO_CONTROLLER_PORT(charge_port); + + cypd_update_port_state(controller, port); + } return; } From 596cee7951a1024b55d2c243f16fb4a7f82741bc Mon Sep 17 00:00:00 2001 From: tyeth Date: Wed, 21 Oct 2026 00:16:24 +0100 Subject: [PATCH 3/3] ci: add hosted-runner tulip build for fork testing Add fork-build.yml: builds the tulip EC image on ubuntu-latest using zmake with the Zephyr SDK toolchain, Framework zephyr fwk-main, and the cros cmsis chromeos-main checkout, per docs/zephyr/out_of_chroot.md. Remove zephyr-build.yml on this branch only: it targets the Framework self-hosted build server and would queue forever on a fork. Co-Authored-By: Claude Fable 5 --- .github/workflows/fork-build.yml | 90 ++++++++++++++++++++++ .github/workflows/zephyr-build.yml | 120 ----------------------------- 2 files changed, 90 insertions(+), 120 deletions(-) create mode 100644 .github/workflows/fork-build.yml delete mode 100644 .github/workflows/zephyr-build.yml diff --git a/.github/workflows/fork-build.yml b/.github/workflows/fork-build.yml new file mode 100644 index 0000000000..77ca5ff674 --- /dev/null +++ b/.github/workflows/fork-build.yml @@ -0,0 +1,90 @@ +name: Fork build (tulip, hosted runner) + +# Self-contained tulip EC build for forks without the Framework +# self-hosted build server. Mirrors .github/workflows/zephyr-build.yml +# (matrix entry: name tulip, ver 3.2.0, zephyr_ref fwk-main, +# cmsis_ref cros/chromeos-main) but assembles the checkout and +# toolchain on a hosted ubuntu runner instead of the pre-provisioned +# ChromiumOS chroot, following docs/zephyr/out_of_chroot.md. + +on: + push: + branches: + - test-* + workflow_dispatch: + +jobs: + build-tulip: + runs-on: ubuntu-latest + env: + ZEPHYR_SDK_VERSION: 0.16.8 + TULIP_VER: "3.2.0" + steps: + - name: Checkout EC (this ref) + uses: actions/checkout@v4 + with: + path: modules/ec + + - name: Checkout Framework zephyr (fwk-main) + uses: actions/checkout@v4 + with: + repository: FrameworkComputer/zephyr + ref: fwk-main + path: deps/zephyr + fetch-depth: 1 + + - name: Checkout CMSIS (cros chromeos-main) + run: | + git clone --depth 1 -b chromeos-main \ + https://chromium.googlesource.com/chromiumos/third_party/zephyr/cmsis \ + "$GITHUB_WORKSPACE/modules/cmsis" + + - name: Install system packages + run: | + sudo apt-get update + sudo apt-get install -y cmake ninja-build device-tree-compiler \ + flex bison swig python3-dev + + - name: Install zmake and python deps + run: | + python3 -m pip install --user "$GITHUB_WORKSPACE/modules/ec/zephyr/zmake" + python3 -m pip install --user pyelftools pyyaml packaging pykwalify colorama pylibfdt + echo "$HOME/.local/bin" >> "$GITHUB_PATH" + + - name: Install binman (u-boot tools) + run: | + git clone --depth 1 https://github.com/u-boot/u-boot.git "$GITHUB_WORKSPACE/deps/u-boot" + echo "TOOL_PATH_binman=$GITHUB_WORKSPACE/deps/u-boot/tools/binman/binman" >> "$GITHUB_ENV" + + - name: Install Zephyr SDK (arm toolchain + host tools) + run: | + cd "$GITHUB_WORKSPACE/deps" + wget -q "https://github.com/zephyrproject-rtos/sdk-ng/releases/download/v${ZEPHYR_SDK_VERSION}/zephyr-sdk-${ZEPHYR_SDK_VERSION}_linux-x86_64_minimal.tar.xz" + tar xf "zephyr-sdk-${ZEPHYR_SDK_VERSION}_linux-x86_64_minimal.tar.xz" + cd "zephyr-sdk-${ZEPHYR_SDK_VERSION}" + ./setup.sh -t arm-zephyr-eabi -h -c + echo "ZEPHYR_SDK_INSTALL_DIR=$GITHUB_WORKSPACE/deps/zephyr-sdk-${ZEPHYR_SDK_VERSION}" >> "$GITHUB_ENV" + + - name: Build tulip (versioned) + working-directory: modules/ec + run: | + zmake -D \ + --zephyr-base "$GITHUB_WORKSPACE/deps/zephyr" \ + --modules-dir "$GITHUB_WORKSPACE/modules" \ + build -t zephyr -v "$TULIP_VER" tulip + + - name: File sha256 + run: sha256sum modules/ec/build/zephyr/tulip/output/ec.bin + + - name: Check version embedded in binary + run: | + strings modules/ec/build/zephyr/tulip/output/ec.bin | grep tulip | tee bin-strings.txt + grep "$(echo "$GITHUB_SHA" | cut -c1-7)" bin-strings.txt + + - name: Generate artifact version + run: echo "VERSIONINFO=$(date -u +'%Y-%m-%d-%H-%M-%S')_$GITHUB_SHA" >> "$GITHUB_ENV" + + - uses: actions/upload-artifact@v4 + with: + name: tulip.${{ env.VERSIONINFO }} + path: modules/ec/build/zephyr/tulip/output diff --git a/.github/workflows/zephyr-build.yml b/.github/workflows/zephyr-build.yml deleted file mode 100644 index e7ef1a91e5..0000000000 --- a/.github/workflows/zephyr-build.yml +++ /dev/null @@ -1,120 +0,0 @@ -name: Build Framework Zephyr EC firmware - -on: - push: - branches: - - marigold* - - lotus* - - azalea* - - fwk* - - pr-* - - test-* - - framework* - # paths: - # - 'zephyr/program/framework/**' - #pull_request: - # branches: - # - marigold - # Dispatch only works on the main branch, so we can't actually use it here - workflow_dispatch: - -jobs: - build: - runs-on: [firmware-buildserver-gh-runner] - - strategy: - # Only build one at a time because we have only one working directory - max-parallel: 1 - matrix: - platform: [ - # Starting with 2.05, tulip is released from fwk-tulip-29169 - { name: tulip, folder: lotus, ver: "3.2.0", sha_len: 7, zephyr_ref: "fwk-main", cmsis_ref: "cros/chromeos-main", remote: "fwk" }, - # Just for reference, not releasing from this branch - { name: lotus, folder: lotus, ver: "4.0.4", sha_len: 7, zephyr_ref: "fwk-main", cmsis_ref: "cros/chromeos-main", remote: "fwk" }, - { name: lilac, folder: lotus, ver: "0.0.0", sha_len: 7, zephyr_ref: "fwk-main", cmsis_ref: "cros/chromeos-main", remote: "fwk" }, - { name: marigold, folder: lotus, ver: "0.0.0", sha_len: 7, zephyr_ref: "fwk-main", cmsis_ref: "cros/chromeos-main", remote: "fwk" }, - - ] - - - # Build not in $GITHUB_HOME but in the chroot env - defaults: - run: - working-directory: /${{ matrix.platform.folder }}/ec_home/chromiumos - - steps: - # Need to manually checkout - # actions/checkout@v4 complains: - # Error: Repository path '/data/ec_home/chromiumos/src/platform/ec' is not under '/data/ec_home/actions-runner/_work/ec/ec' - # See: https://github.com/actions/checkout/issues/197 - - name: Manual checkout - run: | - pushd src/platform/ec - git fetch ${{ matrix.platform.remote }} - git clean -ffdx - # TODO: Doesn't work with on PR, only push - git reset --hard ${{ matrix.platform.remote }}/$GITHUB_REF_NAME - popd - - pushd src/third_party/zephyr/main - git fetch ${{ matrix.platform.remote }} - git clean -ffdx - git reset --hard ${{ matrix.platform.remote }}/${{ matrix.platform.zephyr_ref }} - popd - - pushd src/third_party/zephyr/cmsis - git fetch cros - git clean -ffdx - git reset --hard ${{ matrix.platform.cmsis_ref }} - popd - - - name: Build ${{ matrix.platform.name }} ectool - if: ${{ matrix.platform.zephyr_ref == 'framework-main' || matrix.platform.zephyr_ref == 'fwk-main' }} - run: | - cros_sdk bash -c 'pushd ../platform/ec; BOARD=host make clean' - cros_sdk bash -c 'pushd ../platform/ec; BOARD=host make utils' - - # Note: - # By default cros_sdk changes into src/scripts folder. - # But that will make zmake pick up the scripts git commit SHA - # To embed the ec repo's commit SHA we need to change pwd there - - name: Build ${{ matrix.platform.name }} (versioned) - run: cros_sdk bash -c 'pushd ../platform/ec; zmake -D build -v ${{ matrix.platform.ver }} ${{ matrix.platform.name }}' - - - name: File sha256 - run: sha256sum src/platform/ec/build/zephyr/${{ matrix.platform.name }}/output/ec.bin - - - name: Check version embedded in binary - run: | - strings src/platform/ec/build/zephyr/${{ matrix.platform.name }}/output/ec.bin | grep ${{ matrix.platform.name }} | tee bin-strings.txt - grep $(echo "$GITHUB_SHA" | cut -c1-${{ matrix.platform.sha_len }}) bin-strings.txt - - - name: Generate artifact version - run: | - echo "VERSIONINFO=$(date -u +'%Y-%m-%d-%H-%M-%S')_$GITHUB_SHA" >> $GITHUB_ENV - - - uses: actions/upload-artifact@v4 - with: - name: ${{ matrix.platform.name }}.${{ env.VERSIONINFO }} - path: /${{ matrix.platform.folder }}/ec_home/chromiumos/src/platform/ec/build/zephyr/${{ matrix.platform.name }}/output - - - name: Package dynamic libs with util (enough to run ectool) - if: ${{ matrix.platform.zephyr_ref == 'framework-main' || matrix.platform.zephyr_ref == 'fwk-main' }} - run: | - # Check which libraries it links to - cros_sdk bash -c 'ldd ../platform/ec/build/host/util/ectool' - # Copy libraries to util folder so we can run it with - # sudo LD_LIBRARY_PATH=. ./ectool - cp chroot/usr/lib64/libec.so src/platform/ec/build/host/util/ || true - cp chroot/usr/lib64/libbase-core.so src/platform/ec/build/host/util/ || true - cp chroot/usr/lib64/libc++.so.* src/platform/ec/build/host/util/ || true - cp chroot/usr/lib64/libc++abi.so.* src/platform/ec/build/host/util/ || true - cp chroot/usr/lib64/libabseil_dll.so.* src/platform/ec/build/host/util/ || true - cp chroot/usr/lib64/libfmap.so.* src/platform/ec/build/host/util/ || true - cp chroot/usr/lib64/libbrillo-core.so src/platform/ec/build/host/util/ || true - - - uses: actions/upload-artifact@v4 - if: ${{ matrix.platform.zephyr_ref == 'framework-main' || matrix.platform.zephyr_ref == 'fwk-main' }} - with: - name: util_${{ matrix.platform.name }}.${{ env.VERSIONINFO }} - path: /${{ matrix.platform.folder }}/ec_home/chromiumos/src/platform/ec/build/host/util