Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 16 additions & 6 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,29 @@ jobs:
# ASFW.xcodeproj is generated from project.yml (XcodeGen) and committed.
# Fail if they drifted apart — someone edited project.yml or added source
# files without regenerating, or hand-edited the pbxproj. If this fails
# after an xcodegen version bump changed the output format, regenerate
# locally with the new version and commit.
# after the pinned xcodegen version changes, regenerate locally with the
# same version and commit.
- name: Verify ASFW.xcodeproj matches project.yml (XcodeGen)
run: |
brew install xcodegen
xcodegen --version
xcodegen generate --quiet
# Single source of truth, shared with build.sh, so a contributor's
# locally installed xcodegen can never disagree with this check.
. ./.xcodegen-version
XCODEGEN_ARCHIVE="$RUNNER_TEMP/xcodegen.zip"
XCODEGEN_ROOT="$RUNNER_TEMP/xcodegen-release"
curl --fail --location --retry 3 \
--output "$XCODEGEN_ARCHIVE" \
"https://github.com/yonaskolb/XcodeGen/releases/download/${XCODEGEN_VERSION}/xcodegen.zip"
echo "${XCODEGEN_SHA256} ${XCODEGEN_ARCHIVE}" | shasum -a 256 -c -
ditto -x -k "$XCODEGEN_ARCHIVE" "$XCODEGEN_ROOT"
XCODEGEN_BIN="$XCODEGEN_ROOT/xcodegen/bin/xcodegen"
"$XCODEGEN_BIN" --version
"$XCODEGEN_BIN" generate --quiet
# Diff only the pbxproj: scheme files are cosmetically rewritten by
# any open Xcode (version attr, BuildableName flavor, empty blocks),
# so they flip-flop between xcodegen and Xcode styles; their semantic
# content comes from project.yml either way.
if ! git diff --exit-code --stat -- ASFW.xcodeproj/project.pbxproj project.yml; then
echo "::error::ASFW.xcodeproj is out of sync with project.yml. Run 'xcodegen generate' locally and commit the regenerated project (see README → Building)."
echo "::error::ASFW.xcodeproj is out of sync with project.yml. Regenerate it with XcodeGen ${XCODEGEN_VERSION} and commit the result (see README → Building)."
exit 1
fi

Expand Down
18 changes: 18 additions & 0 deletions .xcodegen-version
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# XcodeGen release pinned for generating ASFW.xcodeproj from project.yml.
#
# Both CI (.github/workflows/build-and-test.yml) and build.sh read this file, so
# the version that regenerates the committed project is the same everywhere.
# Homebrew's xcodegen floats and its output is NOT byte-identical across
# releases (2.45.4 -> 2.46.0 reorders the pbxproj `targets` list), which is why
# the pin exists and why build.sh refuses to regenerate with a different one.
#
# Upgrading — do all three steps together, or CI's drift check will fail:
# 1. bump XCODEGEN_VERSION below
# 2. curl the release zip, put its `shasum -a 256` in XCODEGEN_SHA256
# curl -fL -o /tmp/xcodegen.zip \
# https://github.com/yonaskolb/XcodeGen/releases/download/<ver>/xcodegen.zip
# shasum -a 256 /tmp/xcodegen.zip
# 3. regenerate ASFW.xcodeproj with that exact version and commit it alongside
#
XCODEGEN_VERSION=2.46.0
XCODEGEN_SHA256=4d9e34b62172d645eed6457cac13fc222569974098ef4ee9c3368bedf0196806
2 changes: 1 addition & 1 deletion ASFW.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -3000,8 +3000,8 @@
projectDirPath = "";
projectRoot = "";
targets = (
EE5A446B3669D0171F9606EE /* ASFW */,
0EB9A8DA75D08971084A440A /* ASFWDriver */,
EE5A446B3669D0171F9606EE /* ASFW */,
348672D607701677ABCB567C /* ASFWTests */,
);
};
Expand Down
22 changes: 15 additions & 7 deletions ASFW.xcodeproj/xcshareddata/xcschemes/ASFWDriver.xcscheme
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "2600"
version = "1.3">
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
buildImplicitDependencies = "YES"
runPostActionsOnFailure = "NO">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
Expand All @@ -15,7 +16,7 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0EB9A8DA75D08971084A440A"
BuildableName = "net.mrmidi.ASFW.ASFWDriver.dext"
BuildableName = "ASFWDriver.dext"
BlueprintName = "ASFWDriver"
ReferencedContainer = "container:ASFW.xcodeproj">
</BuildableReference>
Expand All @@ -26,18 +27,21 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
shouldUseLaunchSchemeArgsEnv = "YES"
onlyGenerateCoverageForSpecifiedTargets = "NO">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0EB9A8DA75D08971084A440A"
BuildableName = "net.mrmidi.ASFW.ASFWDriver.dext"
BuildableName = "ASFWDriver.dext"
BlueprintName = "ASFWDriver"
ReferencedContainer = "container:ASFW.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
</Testables>
<CommandLineArguments>
</CommandLineArguments>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
Expand All @@ -54,11 +58,13 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0EB9A8DA75D08971084A440A"
BuildableName = "net.mrmidi.ASFW.ASFWDriver.dext"
BuildableName = "ASFWDriver.dext"
BlueprintName = "ASFWDriver"
ReferencedContainer = "container:ASFW.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<CommandLineArguments>
</CommandLineArguments>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
Expand All @@ -71,11 +77,13 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0EB9A8DA75D08971084A440A"
BuildableName = "net.mrmidi.ASFW.ASFWDriver.dext"
BuildableName = "ASFWDriver.dext"
BlueprintName = "ASFWDriver"
ReferencedContainer = "container:ASFW.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<CommandLineArguments>
</CommandLineArguments>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
Expand Down
57 changes: 56 additions & 1 deletion ASFWDriver/Async/AsyncSubsystemBusReset.cpp
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
// SPDX-License-Identifier: Apache-2.0
#include "AsyncSubsystem.hpp"

#include "Contexts/ATRequestContext.hpp"
#include "Contexts/ATResponseContext.hpp"
#include "Tx/Submitter.hpp"

#include "../Logging/Logging.hpp"
#include "Track/LabelAllocator.hpp"
#include "Track/PayloadRegistry.hpp"

#include <optional>

#include <DriverKit/IOLib.h>

namespace ASFW::Async {
Expand Down Expand Up @@ -167,7 +171,58 @@ void AsyncSubsystem::FlushATContexts() {
if (!txnMgr_) {
return;
}
(void)DrainTxCompletions(nullptr);

// Callers reach here only after StopATContextsOnly(). Once ACTIVE is clear,
// hardware can no longer write a final descriptor status. OHCI 1.2 draft
// §7.2.3.3 (p. 7-14) permits optional controller behavior that leaves
// outstanding AT descriptors without a final status after a bus reset.
// Complete those descriptors as evt_flushed rather than waiting forever.
// Linux uses the same stop-then-flush ordering and maps zero/no-status
// entries to RCODE_GENERATION (context_stop() then at_context_flush(),
// ohci.c:2000-2010); Apple uses stopDMA() then resetDMA() in
// handleBusResetInt().
//
// Confirm ACTIVE is clear per context before flushing. Both references stop
// both contexts unconditionally and then flush unconditionally, but neither
// can prove the stop succeeded: OHCI §7.2.3 lets a context keep the ACTIVE
// bit set past the stop timeout. Flushing such a context would report packets
// that are merely in flight and zero descriptor words the controller is still
// traversing, so skip it and leave the ring for the next scan instead.
auto* atRequest = ResolveAtRequestContext();
auto* atResponse = ResolveAtResponseContext();

const auto flushable = [](auto* context, const char* name) {
if (!context) {
return false;
}
if (context->IsActive()) {
ASFW_LOG_ERROR(Async,
"FlushATContexts: %{public}s still ACTIVE after stop; "
"skipping flush to avoid rewriting live descriptors",
name);
return false;
}
return true;
};

std::optional<ATRequestContext::FlushScope> requestFlush;
if (flushable(atRequest, "AT request")) {
requestFlush.emplace(*atRequest);
}
std::optional<ATResponseContext::FlushScope> responseFlush;
if (flushable(atResponse, "AT response")) {
responseFlush.emplace(*atResponse);
Comment thread
gly11 marked this conversation as resolved.
}
if (!requestFlush && !responseFlush) {
return;
}

const uint32_t drained = DrainTxCompletions("bus-reset-flush");
if (drained > 0) {
ASFW_LOG(Async,
"FlushATContexts: drained %u AT completion(s) during bus-reset flush",
drained);
}
}

void AsyncSubsystem::ConfirmBusGeneration(uint8_t confirmedGeneration) {
Expand Down
Loading