diff --git a/.github/scripts/create-pr-body.sh b/.github/scripts/create-pr-body.sh new file mode 100755 index 0000000000000..521207eca3ec4 --- /dev/null +++ b/.github/scripts/create-pr-body.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +# Script to create PR body +# Arguments: build_time total_time passed failed run_id comparison_section + +BUILD_TIME="$1" +TOTAL_TIME="$2" +PASSED="$3" +FAILED="$4" +RUN_ID="$5" +COMPARISON_SECTION="$6" +REPO="$7" + +cat << EOF +## Summary +This PR has been automatically created after successful completion of all CI stages. + +## Commit Message(s) +\`\`\` +EOF + +cat /tmp/commit_message.txt + +cat << EOF +\`\`\` + +## Test Results + +### ✅ Build Stage +- Status: Passed +- Build Time: ${BUILD_TIME} +- Total Time: ${TOTAL_TIME} +- [View build logs](https://github.com/${REPO}/actions/runs/${RUN_ID}) + +### ✅ Boot Verification +- Status: Passed +- [View boot logs](https://github.com/${REPO}/actions/runs/${RUN_ID}) + +### ✅ Kernel Selftests +- **Passed:** ${PASSED} +- **Failed:** ${FAILED} +- [View kselftest logs](https://github.com/${REPO}/actions/runs/${RUN_ID}) + +${COMPARISON_SECTION} + +--- +🤖 This PR was automatically generated by GitHub Actions +Run ID: ${RUN_ID} +EOF diff --git a/.github/workflows/kernel-build-and-test-x86_64.yml b/.github/workflows/kernel-build-and-test-x86_64.yml index 7f1af38b9e4a2..ec83a3e1ab5be 100644 --- a/.github/workflows/kernel-build-and-test-x86_64.yml +++ b/.github/workflows/kernel-build-and-test-x86_64.yml @@ -5,6 +5,7 @@ permissions: contents: read actions: read packages: read + pull-requests: write jobs: build: @@ -22,7 +23,7 @@ jobs: uses: actions/checkout@v4 with: repository: ctrliq/kernel-container-build - ref: test-stage-separation + ref: maximize-kselftest-coverage path: kernel-container-build token: ${{ secrets.PRIVATE_REPO_ACCESS_TOKEN }} @@ -92,7 +93,7 @@ jobs: uses: actions/checkout@v4 with: repository: ctrliq/kernel-container-build - ref: test-stage-separation + ref: maximize-kselftest-coverage path: kernel-container-build token: ${{ secrets.PRIVATE_REPO_ACCESS_TOKEN }} @@ -146,7 +147,7 @@ jobs: uses: actions/checkout@v4 with: repository: ctrliq/kernel-container-build - ref: test-stage-separation + ref: maximize-kselftest-coverage path: kernel-container-build token: ${{ secrets.PRIVATE_REPO_ACCESS_TOKEN }} @@ -197,36 +198,312 @@ jobs: runs-on: kernel-build needs: test-kselftest if: success() || failure() + outputs: + base_branch: ${{ steps.base_branch.outputs.base_branch }} + comparison_status: ${{ steps.comparison.outputs.comparison_status }} steps: + - name: Checkout kernel source + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Full history needed for reliable merge-base detection + - name: Download current kselftest logs uses: actions/download-artifact@v4 with: name: kselftest-logs-x86_64 path: output-current - - name: Download previous kselftest logs + - name: Determine base branch for comparison + id: base_branch + run: | + BASE_BRANCH="" + + # For PRs, use the base branch directly + if [ -n "${{ github.base_ref }}" ]; then + BASE_BRANCH="${{ github.base_ref }}" + echo "Using PR base branch: $BASE_BRANCH" + else + # For direct pushes, find the common ancestor branch + echo "Not a PR, finding base branch via merge-base" + + # Get all remote branches sorted by commit date (most recent first) + # This ensures we check actively developed branches first + CURRENT_COMMIT=$(git rev-parse HEAD) + BEST_BRANCH="" + CLOSEST_DISTANCE=999999 + + # Only check the 30 most recently updated branches for performance + for remote_branch in $(git for-each-ref --sort=-committerdate --format='%(refname:short)' refs/remotes/origin/ | grep -v 'HEAD\|PR-CHECKER' | head -30); do + branch_name=$(echo "$remote_branch" | sed 's|^origin/||') + + # Skip if it's the current branch + if [ "$branch_name" = "${{ github.ref_name }}" ]; then + continue + fi + + MERGE_BASE=$(git merge-base HEAD "$remote_branch" 2>/dev/null || echo "") + + # Check if this branch shares history and isn't the current commit + if [ -n "$MERGE_BASE" ] && [ "$MERGE_BASE" != "$CURRENT_COMMIT" ]; then + # Calculate distance (number of commits) from merge-base to current HEAD + DISTANCE=$(git rev-list --count ${MERGE_BASE}..HEAD 2>/dev/null || echo "999999") + + # Find the branch with the shortest distance (most recent common ancestor) + if [ "$DISTANCE" -lt "$CLOSEST_DISTANCE" ]; then + CLOSEST_DISTANCE=$DISTANCE + BEST_BRANCH=$branch_name + fi + fi + done + + if [ -n "$BEST_BRANCH" ]; then + BASE_BRANCH=$BEST_BRANCH + echo "Found common ancestor with $BASE_BRANCH (distance: $CLOSEST_DISTANCE commits)" + fi + fi + + if [ -z "$BASE_BRANCH" ]; then + echo "::warning::Could not determine base branch for comparison - no common ancestor found" + echo "::warning::Kselftest comparison will be skipped" + echo "base_branch=" >> $GITHUB_OUTPUT + exit 0 + fi + + echo "base_branch=$BASE_BRANCH" >> $GITHUB_OUTPUT + echo "Base branch for comparison: $BASE_BRANCH" + + - name: Download baseline kselftest logs from base branch + if: steps.base_branch.outputs.base_branch != '' uses: dawidd6/action-download-artifact@v3 with: workflow: kernel-build-and-test-x86_64.yml name: kselftest-logs-x86_64 path: output-previous - branch: ${{ github.ref_name }} + branch: ${{ steps.base_branch.outputs.base_branch }} + workflow_conclusion: success search_artifacts: true skip_unpack: false if_no_artifact_found: warn + # Only search the last 5 successful runs for better performance + run_number: ${{ github.run_number }} + search_depth: 5 continue-on-error: true + timeout-minutes: 3 - name: Compare test results + id: comparison run: | - if [ -f output-previous/kselftests-*.log ]; then - BEFORE=$(grep -a '^ok' output-previous/kselftests-*.log | wc -l) - AFTER=$(grep -a '^ok' output-current/kselftests-*.log | wc -l) - echo "Previous run: $BEFORE passing tests" - echo "Current run: $AFTER passing tests" - if [ "$AFTER" -lt "$BEFORE" ]; then - echo "::warning::Regression detected: $BEFORE -> $AFTER" + # Check if we have a base branch to compare against + if [ -z "${{ steps.base_branch.outputs.base_branch }}" ]; then + echo "::warning::No base branch found for comparison" + echo "::warning::Kselftest comparison will be skipped" + echo "comparison_status=skipped" >> $GITHUB_OUTPUT + echo "comparison_message=No base branch found - unable to determine merge target" >> $GITHUB_OUTPUT + exit 0 + fi + + # Check if baseline logs exist + if ls output-previous/kselftests-*.log 1> /dev/null 2>&1; then + # Compare passing tests (ok) + BEFORE_PASS=$(grep -a '^ok' output-previous/kselftests-*.log | wc -l || echo "0") + AFTER_PASS=$(grep -a '^ok' output-current/kselftests-*.log | wc -l || echo "0") + + # Compare failing tests (not ok) + BEFORE_FAIL=$(grep -a '^not ok' output-previous/kselftests-*.log | wc -l || echo "0") + AFTER_FAIL=$(grep -a '^not ok' output-current/kselftests-*.log | wc -l || echo "0") + + echo "### Kselftest Comparison" + echo "Baseline (from ${{ steps.base_branch.outputs.base_branch }}): $BEFORE_PASS passing, $BEFORE_FAIL failing" + echo "Current (${{ github.ref_name }}): $AFTER_PASS passing, $AFTER_FAIL failing" + + # Calculate differences + PASS_DIFF=$((AFTER_PASS - BEFORE_PASS)) + FAIL_DIFF=$((AFTER_FAIL - BEFORE_FAIL)) + + echo "Pass difference: $PASS_DIFF" + echo "Fail difference: $FAIL_DIFF" + + # Check for regression (more than 3 tests difference) + REGRESSION=0 + + if [ $PASS_DIFF -lt -3 ]; then + echo "::error::Regression detected: $PASS_DIFF passing tests (threshold: -3)" + REGRESSION=1 + fi + + if [ $FAIL_DIFF -gt 3 ]; then + echo "::error::Regression detected: +$FAIL_DIFF failing tests (threshold: +3)" + REGRESSION=1 + fi + + if [ $REGRESSION -eq 1 ]; then + echo "::error::Test regression exceeds acceptable threshold of 3 tests" + echo "comparison_status=failed" >> $GITHUB_OUTPUT + echo "comparison_message=Regression detected: Pass diff: $PASS_DIFF, Fail diff: $FAIL_DIFF (threshold: ±3)" >> $GITHUB_OUTPUT + exit 1 + else + echo "::notice::Test results within acceptable range (threshold: ±3 tests)" + echo "comparison_status=passed" >> $GITHUB_OUTPUT + echo "comparison_message=Baseline: $BEFORE_PASS passing, $BEFORE_FAIL failing | Current: $AFTER_PASS passing, $AFTER_FAIL failing" >> $GITHUB_OUTPUT fi else - echo "No previous results found, skipping comparison" + echo "::warning::No baseline test results found for branch ${{ steps.base_branch.outputs.base_branch }}" + echo "::notice::Cannot compare against base branch - artifacts may not exist or have expired (7-day retention)" + echo "::notice::Skipping comparison - PR will still be created with warning" + echo "comparison_status=skipped" >> $GITHUB_OUTPUT + echo "comparison_message=No baseline results available from ${{ steps.base_branch.outputs.base_branch }}" >> $GITHUB_OUTPUT + fi + create-pr: + name: Create Pull Request + runs-on: kernel-build + needs: [build, boot, test-kselftest, compare-results] + if: success() || failure() + + steps: + - name: Check if tests passed and no regressions + run: | + # Skip PR if any test stage failed + if [ "${{ needs.build.result }}" != "success" ] || \ + [ "${{ needs.boot.result }}" != "success" ] || \ + [ "${{ needs.test-kselftest.result }}" != "success" ]; then + echo "One or more test stages failed, skipping PR creation" + exit 1 + fi + + # Skip PR if regression was detected (but allow if comparison was skipped/unavailable) + if [ "${{ needs.compare-results.outputs.comparison_status }}" = "failed" ]; then + echo "Test regression detected, skipping PR creation" + exit 1 + fi + + echo "All test stages passed and no regressions detected, proceeding with PR creation" + + - name: Checkout kernel source + uses: actions/checkout@v4 + with: + fetch-depth: 50 # Shallow clone: enough for commit messages and logs + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Download kernel compilation logs + uses: actions/download-artifact@v4 + with: + name: kernel-compilation-logs-x86_64 + path: artifacts/build + + - name: Download boot logs + uses: actions/download-artifact@v4 + with: + name: boot-logs-x86_64 + path: artifacts/boot + + - name: Download kselftest logs + uses: actions/download-artifact@v4 + with: + name: kselftest-logs-x86_64 + path: artifacts/test + + - name: Extract test statistics + id: stats + run: | + PASSED=$(grep -a '^ok' artifacts/test/kselftests-*.log | wc -l || echo "0") + FAILED=$(grep -a '^not ok' artifacts/test/kselftests-*.log | wc -l || echo "0") + echo "passed=$PASSED" >> $GITHUB_OUTPUT + echo "failed=$FAILED" >> $GITHUB_OUTPUT + + - name: Extract build timers + id: build_info + run: | + BUILD_TIME=$(grep -oP '\[TIMER\]\{BUILD\}:\s*\K[0-9]+' artifacts/build/kernel-build.log | head -1 || echo "N/A") + TOTAL_TIME=$(grep -oP '\[TIMER\]\{TOTAL\}\s*\K[0-9]+' artifacts/build/kernel-build.log | head -1 || echo "N/A") + echo "build_time=${BUILD_TIME}s" >> $GITHUB_OUTPUT + echo "total_time=${TOTAL_TIME}s" >> $GITHUB_OUTPUT + + - name: Get commit information + id: commit_msg + run: | + # Count commits since origin/main (or appropriate base branch) + BASE_BRANCH="main" + if ! git rev-parse origin/$BASE_BRANCH >/dev/null 2>&1; then + # Try other common base branch names + for branch in master lts-9.2 lts-9; do + if git rev-parse origin/$branch >/dev/null 2>&1; then + BASE_BRANCH=$branch + break + fi + done + fi + + COMMIT_COUNT=$(git rev-list --count origin/$BASE_BRANCH..HEAD 2>/dev/null || echo "1") + + if [ "$COMMIT_COUNT" -eq "1" ]; then + # Single commit: use commit subject + git log -1 --pretty=%s > /tmp/commit_subject.txt + COMMIT_SUBJECT=$(cat /tmp/commit_subject.txt) + echo "commit_subject=$COMMIT_SUBJECT" >> $GITHUB_OUTPUT + + # Save full commit message to file + git log -1 --pretty=%B > /tmp/commit_message.txt + else + # Multiple commits: create summary + echo "commit_subject=Multiple patches tested ($COMMIT_COUNT commits)" >> $GITHUB_OUTPUT + + # Get all commit messages and save to file + git log origin/$BASE_BRANCH..HEAD --pretty=format:"### %s%n%n%b%n---" > /tmp/commit_message.txt + fi + + - name: Create Pull Request + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # Reuse base branch from compare-results stage (already computed) + BASE_BRANCH="${{ needs.compare-results.outputs.base_branch }}" + + if [ -z "$BASE_BRANCH" ]; then + echo "ERROR: Could not determine base branch for PR (compare-results did not find one)" + exit 1 + fi + + echo "Creating PR from ${{ github.ref_name }} to $BASE_BRANCH" + + # Determine comparison status message + COMPARISON_RESULT="${{ needs.compare-results.result }}" + if [ "$COMPARISON_RESULT" = "success" ]; then + COMPARISON_SECTION="### ✅ Test Comparison + - Status: Passed - Within acceptable threshold (±3 tests) + - Compared against: $BASE_BRANCH" + else + COMPARISON_SECTION="### ⚠️ Test Comparison + - Status: Skipped + - Reason: No baseline test results available from $BASE_BRANCH + - **Note:** Manual review recommended to ensure no regressions" + fi + + # Create PR body using script + chmod +x .github/scripts/create-pr-body.sh + .github/scripts/create-pr-body.sh \ + "${{ steps.build_info.outputs.build_time }}" \ + "${{ steps.build_info.outputs.total_time }}" \ + "${{ steps.stats.outputs.passed }}" \ + "${{ steps.stats.outputs.failed }}" \ + "${{ github.run_id }}" \ + "$COMPARISON_SECTION" \ + "${{ github.repository }}" \ + > pr_body.md + + # Check if PR already exists + EXISTING_PR=$(gh pr list --head "${{ github.ref_name }}" --base "$BASE_BRANCH" --json number --jq '.[0].number' || echo "") + + if [ -n "$EXISTING_PR" ]; then + echo "PR #$EXISTING_PR already exists, updating it" + gh pr edit "$EXISTING_PR" \ + --title "[${{ github.ref_name }}] ${{ steps.commit_msg.outputs.commit_subject }}" \ + --body-file pr_body.md + else + echo "Creating new PR from ${{ github.ref_name }} to $BASE_BRANCH" + gh pr create \ + --base "$BASE_BRANCH" \ + --head "${{ github.ref_name }}" \ + --title "[${{ github.ref_name }}] ${{ steps.commit_msg.outputs.commit_subject }}" \ + --body-file pr_body.md fi diff --git a/drivers/firmware/arm_scpi.c b/drivers/firmware/arm_scpi.c index ddf0b9ff9e15c..435d0e2658a42 100644 --- a/drivers/firmware/arm_scpi.c +++ b/drivers/firmware/arm_scpi.c @@ -815,7 +815,7 @@ static int scpi_init_versions(struct scpi_drvinfo *info) info->firmware_version = le32_to_cpu(caps.platform_version); } /* Ignore error if not implemented */ - if (scpi_info->is_legacy && ret == -EOPNOTSUPP) + if (info->is_legacy && ret == -EOPNOTSUPP) return 0; return ret; @@ -913,13 +913,14 @@ static int scpi_probe(struct platform_device *pdev) struct resource res; struct device *dev = &pdev->dev; struct device_node *np = dev->of_node; + struct scpi_drvinfo *scpi_drvinfo; - scpi_info = devm_kzalloc(dev, sizeof(*scpi_info), GFP_KERNEL); - if (!scpi_info) + scpi_drvinfo = devm_kzalloc(dev, sizeof(*scpi_drvinfo), GFP_KERNEL); + if (!scpi_drvinfo) return -ENOMEM; if (of_match_device(legacy_scpi_of_match, &pdev->dev)) - scpi_info->is_legacy = true; + scpi_drvinfo->is_legacy = true; count = of_count_phandle_with_args(np, "mboxes", "#mbox-cells"); if (count < 0) { @@ -927,19 +928,19 @@ static int scpi_probe(struct platform_device *pdev) return -ENODEV; } - scpi_info->channels = devm_kcalloc(dev, count, sizeof(struct scpi_chan), - GFP_KERNEL); - if (!scpi_info->channels) + scpi_drvinfo->channels = + devm_kcalloc(dev, count, sizeof(struct scpi_chan), GFP_KERNEL); + if (!scpi_drvinfo->channels) return -ENOMEM; - ret = devm_add_action(dev, scpi_free_channels, scpi_info); + ret = devm_add_action(dev, scpi_free_channels, scpi_drvinfo); if (ret) return ret; - for (; scpi_info->num_chans < count; scpi_info->num_chans++) { + for (; scpi_drvinfo->num_chans < count; scpi_drvinfo->num_chans++) { resource_size_t size; - int idx = scpi_info->num_chans; - struct scpi_chan *pchan = scpi_info->channels + idx; + int idx = scpi_drvinfo->num_chans; + struct scpi_chan *pchan = scpi_drvinfo->channels + idx; struct mbox_client *cl = &pchan->cl; struct device_node *shmem = of_parse_phandle(np, "shmem", idx); @@ -986,45 +987,53 @@ static int scpi_probe(struct platform_device *pdev) return ret; } - scpi_info->commands = scpi_std_commands; + scpi_drvinfo->commands = scpi_std_commands; - platform_set_drvdata(pdev, scpi_info); + platform_set_drvdata(pdev, scpi_drvinfo); - if (scpi_info->is_legacy) { + if (scpi_drvinfo->is_legacy) { /* Replace with legacy variants */ scpi_ops.clk_set_val = legacy_scpi_clk_set_val; - scpi_info->commands = scpi_legacy_commands; + scpi_drvinfo->commands = scpi_legacy_commands; /* Fill priority bitmap */ for (idx = 0; idx < ARRAY_SIZE(legacy_hpriority_cmds); idx++) set_bit(legacy_hpriority_cmds[idx], - scpi_info->cmd_priority); + scpi_drvinfo->cmd_priority); } - ret = scpi_init_versions(scpi_info); + scpi_info = scpi_drvinfo; + + ret = scpi_init_versions(scpi_drvinfo); if (ret) { dev_err(dev, "incorrect or no SCP firmware found\n"); + scpi_info = NULL; return ret; } - if (scpi_info->is_legacy && !scpi_info->protocol_version && - !scpi_info->firmware_version) + if (scpi_drvinfo->is_legacy && !scpi_drvinfo->protocol_version && + !scpi_drvinfo->firmware_version) dev_info(dev, "SCP Protocol legacy pre-1.0 firmware\n"); else dev_info(dev, "SCP Protocol %lu.%lu Firmware %lu.%lu.%lu version\n", FIELD_GET(PROTO_REV_MAJOR_MASK, - scpi_info->protocol_version), + scpi_drvinfo->protocol_version), FIELD_GET(PROTO_REV_MINOR_MASK, - scpi_info->protocol_version), + scpi_drvinfo->protocol_version), FIELD_GET(FW_REV_MAJOR_MASK, - scpi_info->firmware_version), + scpi_drvinfo->firmware_version), FIELD_GET(FW_REV_MINOR_MASK, - scpi_info->firmware_version), + scpi_drvinfo->firmware_version), FIELD_GET(FW_REV_PATCH_MASK, - scpi_info->firmware_version)); - scpi_info->scpi_ops = &scpi_ops; + scpi_drvinfo->firmware_version)); + + scpi_drvinfo->scpi_ops = &scpi_ops; - return devm_of_platform_populate(dev); + ret = devm_of_platform_populate(dev); + if (ret) + scpi_info = NULL; + + return ret; } static const struct of_device_id scpi_of_match[] = {