Skip to content
Merged
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
4 changes: 4 additions & 0 deletions ASFW.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@
46EAC460C610E97D83C42F43 /* AVCMusicCapabilities.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 38B33C0D51014568208DBB2A /* AVCMusicCapabilities.cpp */; };
47C7D2797F3974490450A45A /* AVCNestedChannelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB92C50A509F45F51EFD3F03 /* AVCNestedChannelTests.swift */; };
47F41CED6EB2EE793A31B6E5 /* ASFWAudioDriverControls.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 018751CF8CA051FDDE9B2B06 /* ASFWAudioDriverControls.cpp */; };
47F7CC1D374745AF715C8AB2 /* PhysicalRequestFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AE9C2F9E30033532DA95E435 /* PhysicalRequestFilter.cpp */; };
48359CD69DF9A8F5868E12C0 /* ASFWMCPLiveDriverControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1391A71639068D8291B32AF /* ASFWMCPLiveDriverControl.swift */; };
4A7F808CC9DBBBED16B2F07A /* ConfigROMBuilder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 290CF8A5D9D6CABD1B3BF37A /* ConfigROMBuilder.cpp */; };
4A8DB4DE00645A2458BAF883 /* MidiNubPublisher.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2376C0878D2E3E97CF4DD0CE /* MidiNubPublisher.cpp */; };
Expand Down Expand Up @@ -1401,6 +1402,7 @@
AE208856E70C5C9AC1A54F71 /* ASFW-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ASFW-Bridging-Header.h"; sourceTree = "<group>"; };
AE3316AEFE76C1A5D9641992 /* MidiTxReservation.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MidiTxReservation.hpp; sourceTree = "<group>"; };
AE6917642BC190CEA2F5E7E3 /* BusManagerRuntimeState.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = BusManagerRuntimeState.hpp; sourceTree = "<group>"; };
AE9C2F9E30033532DA95E435 /* PhysicalRequestFilter.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = PhysicalRequestFilter.cpp; sourceTree = "<group>"; };
AEC88EDC2EC1E38DC23B8916 /* AudioCoordinator.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = AudioCoordinator.hpp; sourceTree = "<group>"; };
AED113166DE4061F3A41FC54 /* BoundedDequeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoundedDequeTests.swift; sourceTree = "<group>"; };
AED823683733E47EADBB95EB /* DriverConnector+IRM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DriverConnector+IRM.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3554,6 +3556,7 @@
A418B8B548743D3952A8089B /* OHCIConstants.hpp */,
C197F16F4A4D565C13DA4286 /* OHCIDescriptors.hpp */,
2AEC69885AF2551714DE1936 /* OHCIEventCodes.hpp */,
AE9C2F9E30033532DA95E435 /* PhysicalRequestFilter.cpp */,
E90F7A6B5C08D9C94CD93034 /* RegisterMap.hpp */,
);
path = Hardware;
Expand Down Expand Up @@ -4562,6 +4565,7 @@
E8E793E5929460B3A42C034D /* PayloadRegistry.cpp in Sources */,
3C130CEE2A5FE017326B568E /* PcmSlotCodec.cpp in Sources */,
836FE3225BA2D368808EFC48 /* PhyCommand.cpp in Sources */,
47F7CC1D374745AF715C8AB2 /* PhysicalRequestFilter.cpp in Sources */,
3ECDDF5924BE520E1750A8FF /* PostResetTimingCoordinator.cpp in Sources */,
270027C27155433C83E72998 /* PowerLinkPolicyCoordinator.cpp in Sources */,
3455F76072D6ED0ED7E7B77E /* ROMReader.cpp in Sources */,
Expand Down
10 changes: 10 additions & 0 deletions ASFWDriver/ASFWDriver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,16 @@ void ExecuteRuntimeTeardown(ServiceContext& ctx, const QuiescePlan& plan) {
ctx.deps.avcDiscovery->Shutdown();
}
ASFW::Protocols::SBP2::SBP2BridgeHub::Clear();
// The bridge releases sessions whose commands the target may still be
// serving by DMA, and their logouts may fail on a bus that is going down.
// Shut the physical response unit first: then no target can reach a
// released buffer, and a failed logout needs no bus reset.
if (ctx.deps.hardware) {
ctx.deps.hardware->ClearPhysicalRequestFilters();
}
if (ctx.deps.sbp2SessionRegistry) {
ctx.deps.sbp2SessionRegistry->PrepareForTeardown();
}
if (ctx.sbp2Bridge) {
ctx.sbp2Bridge->Shutdown();
ctx.sbp2Bridge.reset();
Expand Down
1 change: 1 addition & 0 deletions ASFWDriver/Async/AsyncSubsystemLifecycle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,7 @@ void AsyncSubsystem::Teardown(bool disableHardware) {
if (disableHardware && hardware_) {
hardware_->SetInterruptMask(0xFFFFFFFFu, false);
hardware_->ClearLinkControlBits(Driver::LinkControlBits::kRcvPhyPkt);
hardware_->ClearPhysicalRequestFilters();
}

// CRITICAL: Deactivate completion queue BEFORE stopping contexts
Expand Down
3 changes: 3 additions & 0 deletions ASFWDriver/Controller/ControllerCoreInterrupts.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ IsochContextEvents ControllerCore::HandleInterrupt(const InterruptSnapshot& snap
HandleFaultInterrupts(events);
NotifyBusResetCoordinator(events, snapshot.timestamp);
if ((events & IntEventBits::kBusReset) != 0U) {
// Node numbers are reassigned by the reset; no node stays admitted to
// physical DMA until its session re-enables it for the new generation.
hw.ClearPhysicalRequestFilters();
auto access = hw.TryBeginAccess();
if (!access) return {};
// SelfIDCount.selfIDGeneration already holds the new generation at the
Expand Down
7 changes: 5 additions & 2 deletions ASFWDriver/Controller/ControllerCoreLifecycle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -725,8 +725,11 @@ kern_return_t ControllerCore::InitialiseHardware(IOService* provider) {
return configRomStatus;
}

// Step 7: Set Physical Upper Bound (256MB CSR address range)
// TODO(ASFW-DMA): Confirm whether remote DMA still requires this register programming.
// Step 7: bound physical responses to the 4 GiB every DMA IOVA lives in
// (AllocateDMA asks for 32-bit addresses), as Linux ohci_enable does.
if (auto access = hw.TryBeginAccess()) {
access.WriteAndFlush(Register32::kPhyUpperBound, ASFW::Driver::kPhysicalUpperBound);
}
// Per Linux ohci_enable(): Don't pre-write NodeID; bus reset will assign it from Self-ID
// The kProvisionalNodeId value would be immediately overwritten anyway
hw.SetLinkControlBits(ASFW::Driver::kDefaultLinkControl);
Expand Down
9 changes: 9 additions & 0 deletions ASFWDriver/Hardware/HardwareInterface.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

#include <atomic>
#include <expected>
#include <optional>
#include <utility> // for std::pair

Expand Down Expand Up @@ -32,6 +33,8 @@ class IAsyncControllerPort;

namespace ASFW::Driver {

enum class PhysicalDmaError : uint8_t { InvalidNode, HardwareGone, StaleGeneration };

/// Result of a local autonomous IRM CSR compare-swap operation (OHCI §5.5).
struct LocalCSRLockResult {
enum class Status : uint8_t {
Expand Down Expand Up @@ -110,6 +113,12 @@ class HardwareInterface {
[[nodiscard]] PciInterruptState ReadPciInterruptState() const noexcept;
void SetLinkControlBits(uint32_t bits);
void ClearLinkControlBits(uint32_t bits);
// Admit one local node's physical requests for the current generation, the
// way Linux ohci_enable_phys_dma does. Nothing disables a node; the filter
// is cleared as a whole at bus reset and teardown.
[[nodiscard]] std::expected<void, PhysicalDmaError>
EnablePhysicalRequests(uint16_t node, uint16_t generation) noexcept;
void ClearPhysicalRequestFilters() noexcept;
void ClearIntEvents(uint32_t mask);
// Acknowledges the caller-selected global bits, then reads and clears each
// signalled isoch direction. One readback completes the entire batch before
Expand Down
3 changes: 3 additions & 0 deletions ASFWDriver/Hardware/OHCIConstants.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ namespace ASFW::Driver {
// AR Filter Constants (OHCI §7.4)
// Bit 31 in AsReqFilterHiSet = accept all async requests
constexpr uint32_t kAsReqAcceptAllMask = 0x80000000u;
// PhysicalUpperBound: physical responses serve the first 4 GiB, where every
// IOVA this driver hands a target lives (Linux ohci.c:2348, core.h:289).
constexpr uint32_t kPhysicalUpperBound = 0x00010000u;

// Default link control configuration used during controller initialization.
// Keep the local cycle timer running for cycleLost/cycleSynch accounting, but
Expand Down
52 changes: 52 additions & 0 deletions ASFWDriver/Hardware/PhysicalRequestFilter.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#include "HardwareInterface.hpp"
#include "OHCIConstants.hpp"
#include "RegisterMap.hpp"
#include "../Logging/Logging.hpp"

namespace ASFW::Driver {

std::expected<void, PhysicalDmaError>
HardwareInterface::EnablePhysicalRequests(uint16_t node, uint16_t generation) noexcept {
const uint16_t bus = node & 0xFFC0u;
const uint16_t phy = node & 0x3Fu;
if ((bus != 0 && bus != 0xFFC0u) || phy == 63) {
return std::unexpected(PhysicalDmaError::InvalidNode);
}
auto access = TryBeginAccess();
if (!access) {
return std::unexpected(PhysicalDmaError::HardwareGone);
}
// Linux ohci.c:2615: a stale generation must not arm a node number the
// reset may have handed to another device. Linux checks under the lock its
// reset work also takes; we have no such lock against the interrupt path,
// so the check is repeated after the write and the bit taken back if a
// reset slipped in between.
const auto stale = [&access, generation]() {
const uint32_t current = (access.Read(Register32::kSelfIDCount) &
SelfIDCountBits::kGenerationMask) >> SelfIDCountBits::kGenerationShift;
return current != (generation & 0xFFu) ||
(access.Read(Register32::kIntEvent) & IntEventBits::kBusReset) != 0;
};
if (stale()) {
return std::unexpected(PhysicalDmaError::StaleGeneration);
}
const Register32 set = phy < 32 ? Register32::kPhyReqFilterLoSet : Register32::kPhyReqFilterHiSet;
const Register32 clear = phy < 32 ? Register32::kPhyReqFilterLoClear : Register32::kPhyReqFilterHiClear;
const uint32_t bit = 1u << (phy % 32);
access.WriteAndFlush(set, bit);
if (stale()) {
access.WriteAndFlush(clear, bit);
return std::unexpected(PhysicalDmaError::StaleGeneration);
}
ASFW_LOG(Hardware, "[PhysicalDMA] node=%u gen=%u enabled", phy, generation);
return {};
}

void HardwareInterface::ClearPhysicalRequestFilters() noexcept {
if (auto access = TryBeginAccess()) {
access.Write(Register32::kPhyReqFilterHiClear, 0xFFFFFFFFu);
access.WriteAndFlush(Register32::kPhyReqFilterLoClear, 0xFFFFFFFFu);
}
}

} // namespace ASFW::Driver
92 changes: 85 additions & 7 deletions ASFWDriver/Protocols/SBP2/AddressSpaceManager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <cstdint>
#include <cstring>
#include <optional>
#include <ranges>
#include <span>
#include <unordered_map>
#include <vector>
Expand Down Expand Up @@ -152,6 +153,64 @@ class AddressSpaceManager {
return kr;
}

// A buffer the target reaches through the OHCI physical response unit: its
// 1394 address is the buffer's own IOVA (below 4 GiB, addressHi 0), so the
// controller serves the target's reads and writes by DMA with no software
// in the path. Apple (IOFireWireSBP2ORB.cpp:1002-1041) and Linux
// (sbp2_map_scatterlist) carry SBP-2 data this way. The range stays in the
// table so the CPU side can read it by handle; it has no shadow copy.
kern_return_t AllocatePhysicalRange(void* owner,
uint32_t length,
uint64_t* outHandle,
AddressRangeMeta* outMeta = nullptr) {
if (!lock_ || !outHandle || length == 0) {
return kIOReturnBadArgument;
}

AddressRange range{};
range.owner = owner;
range.physical = true;
range.meta.length = length;

IOLockLock(lock_);
const kern_return_t kr = AllocateBacking(range);
if (kr != kIOReturnSuccess) {
IOLockUnlock(lock_);
return kr;
}
uint64_t address = range.deviceAddress;
if (!range.hasBacking) {
if (hardware_) {
IOLockUnlock(lock_);
return kIOReturnNoMemory;
}
// Host tests have no DMA: stand in with a unique sub-4 GiB address.
address = kHostTestPhysicalBase + hostTestPhysicalCursor_;
hostTestPhysicalCursor_ += AlignUp(length, 4096);
range.buffer.resize(length, 0);
}
for (const auto& [handle, existing] : ranges_) {
if (RangesOverlap(address, length, existing.meta.address, existing.meta.length)) {
CleanupBacking(range);
IOLockUnlock(lock_);
return kIOReturnNoSpace;
}
}
range.meta.handle = nextHandle_++;
range.meta.address = address;
range.meta.addressHi = static_cast<uint16_t>(address >> 32);
range.meta.addressLo = static_cast<uint32_t>(address & 0xFFFF'FFFFULL);

const uint64_t handle = range.meta.handle;
if (outMeta) {
*outMeta = range.meta;
}
ranges_.emplace(handle, std::move(range));
IOLockUnlock(lock_);
*outHandle = handle;
return kIOReturnSuccess;
}

kern_return_t DeallocateAddressRange(void* owner, uint64_t handle) {
if (!lock_ || handle == 0) {
return kIOReturnBadArgument;
Expand Down Expand Up @@ -216,8 +275,8 @@ class AddressSpaceManager {
return kIOReturnNoSpace;
}

outData->assign(range.buffer.begin() + static_cast<std::size_t>(offset),
range.buffer.begin() + static_cast<std::size_t>(offset + length));
const auto bytes = Storage(range).subspan(offset, length);
outData->assign(bytes.begin(), bytes.end());
IOLockUnlock(lock_);
return kIOReturnSuccess;
}
Expand Down Expand Up @@ -349,8 +408,7 @@ class AddressSpaceManager {
}

const uint32_t offset = static_cast<uint32_t>(address - range->meta.address);
std::memcpy(outValue,
range->buffer.data() + static_cast<std::size_t>(offset),
std::memcpy(outValue, Storage(*range).subspan(offset, sizeof(uint32_t)).data(),
sizeof(uint32_t));

IOLockUnlock(lock_);
Expand Down Expand Up @@ -429,6 +487,7 @@ class AddressSpaceManager {
static constexpr uint32_t kAutoAddressWindowStartLo = 0x0010'0000u;
static constexpr uint32_t kAutoAddressWindowEndLo = 0x0FFF'FFFFu;
static constexpr uint64_t kAutoAddressAlignment = 8ULL;
static constexpr uint64_t kHostTestPhysicalBase = 0x4000'0000ULL;

static constexpr std::size_t kDebugLabelCapacity = 32;

Expand All @@ -445,8 +504,18 @@ class AddressSpaceManager {
uint8_t* mappedBytes{nullptr};
uint64_t deviceAddress{0};
bool hasBacking{false};
bool physical{false};
};

// Where the bytes live: the DMA mapping for a physical range (the target
// writes it directly), the shadow copy for a software-served range.
static std::span<const uint8_t> Storage(const AddressRange& range) {
if (range.physical && range.hasBacking) {
return {range.mappedBytes, range.meta.length};
}
return range.buffer;
}

static void CopyDebugLabel(AddressRange& range, const char* label) {
range.debugLabel.fill('\0');
if (!label) {
Expand Down Expand Up @@ -606,9 +675,11 @@ class AddressSpaceManager {
#endif
}

// Software-served ranges are written by the CPU per request and stay
// uncached; a physical range is filled by the device and read once.
IOMemoryMap* mapping = nullptr;
const kern_return_t kr = dma->descriptor->CreateMapping(
kIOMemoryMapCacheModeInhibit,
range.physical ? 0 : kIOMemoryMapCacheModeInhibit,
0,
0,
size,
Expand All @@ -632,7 +703,11 @@ class AddressSpaceManager {
return kIOReturnNoMemory;
}

ZeroMappedBytes(mapped, size);
if (range.physical) {
std::ranges::fill(std::span<uint8_t>{mapped, size}, uint8_t{0});
} else {
ZeroMappedBytes(mapped, size);
}
OSSynchronizeIO();

range.descriptor = std::move(dma->descriptor);
Expand Down Expand Up @@ -744,7 +819,9 @@ class AddressSpaceManager {
static_cast<unsigned long>(reinterpret_cast<uintptr_t>(data.data()) & 0x7ULL),
static_cast<unsigned long>(reinterpret_cast<uintptr_t>(range.buffer.data()) & 0x7ULL),
static_cast<unsigned long>(reinterpret_cast<uintptr_t>(range.mappedBytes) & 0x7ULL));
CopyPayloadBytes(range.buffer.data() + static_cast<std::size_t>(offset), data);
if (!range.buffer.empty()) {
CopyPayloadBytes(range.buffer.data() + static_cast<std::size_t>(offset), data);
}

if (range.hasBacking && range.mappedBytes) {
CopyPayloadBytes(range.mappedBytes + static_cast<std::size_t>(offset), data);
Expand All @@ -756,6 +833,7 @@ class AddressSpaceManager {
ASFW::Driver::HardwareInterface* hardware_{nullptr};
IOLock* lock_{nullptr};
uint64_t nextHandle_{1};
uint64_t hostTestPhysicalCursor_{0};
std::unordered_map<uint64_t, AddressRange> ranges_;
};

Expand Down
7 changes: 7 additions & 0 deletions ASFWDriver/Protocols/SBP2/SCSICommandSet.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ struct CommandRequest {
std::vector<uint8_t> outgoingPayload{};
uint32_t timeoutMs{2000};
bool captureSenseData{false};
// Bus address of the caller's own DMA-mapped buffer, below 4 GiB. When set,
// the target moves the data there directly: no bounce buffer, no copy, and
// outgoingPayload stays empty for data-out.
uint64_t dataIOVA{0};

[[nodiscard]] bool HasTransferBuffer() const noexcept {
return transferLength > 0;
Expand All @@ -39,6 +43,9 @@ struct CommandResult {
bool scsiStatusValid{false};
std::vector<uint8_t> payload{};
std::vector<uint8_t> senseData{};
// Bytes the target moved straight into the caller's buffer (dataIOVA);
// payload stays empty for those commands.
uint32_t inPlaceBytes{0};
};

// SBP-2 Annex B packs autosense into the status block's command-set-dependent
Expand Down
Loading