-
Notifications
You must be signed in to change notification settings - Fork 2
Apple API Integration NuDCL Guide
- Introduction to NuDCL
- Fundamental Concepts
-
The NuDCL Pool (
IOFireWireNuDCLPoolInterface) - Building NuDCL Programs
- NuDCL Command Types
- Configuring NuDCL Commands (API Reference)
- Executing and Modifying NuDCL Programs
- Practical Usage Scenarios
- Key I/O Kit Interfaces
- Debugging and Troubleshooting
FireWire (IEEE 1394) supports isochronous data transfer, a mode designed for time-sensitive data like digital audio and video. Unlike asynchronous transfers (which guarantee delivery but not timing), isochronous transfers guarantee bandwidth and low latency by allocating specific time slots on the bus for data packets. This ensures smooth, continuous playback or recording without interruptions caused by bus contention.
Managing these high-speed, time-critical data streams requires precise control over how data is moved between system memory and the FireWire interface hardware. This is where the Data Stream Control Language (DCL) concept comes into play. DCL provides a hardware-agnostic way for developers to define sequences of data transfer operations.
NuDCL (New DCL) is the modern macOS I/O Kit API for constructing and managing these DCL programs for FireWire isochronous operations. Its primary purpose is to provide a flexible and powerful interface for:
- Defining complex sequences of send and receive operations for isochronous packets.
- Utilizing scatter/gather DMA, allowing data to be transferred to or from multiple non-contiguous memory buffers efficiently.
- Controlling the flow of the data stream programmatically (e.g., creating loops for continuous streaming).
- Receiving status feedback and timestamps for executed operations.
- Enabling dynamic modification of data streams while they are active (e.g., swapping buffers).
By abstracting the low-level hardware details, NuDCL allows developers to focus on the logic of their isochronous data handling without needing deep knowledge of specific FireWire controller chipsets.
NuDCL is an integral part of the I/O Kit FireWire Family (IOFireWireFamily.kext) on modern macOS. I/O Kit is the object-oriented framework for developing device drivers and accessing hardware services.
Within this framework:
- FireWire devices are represented by
IOFireWireDeviceuser client objects. - Specific functionalities, like isochronous streaming, are accessed through specialized interfaces obtained from the
IOFireWireDevice. - The
IOFireWireNuDCLPoolInterfaceis obtained from anIOFireWireDeviceand serves as the entry point for creating NuDCL programs. - These NuDCL programs are then executed by associating them with an
IOFireWireLocalIsochPortInterface, which represents a talker or listener endpoint on the local machine. - The coordination of starting, stopping, and managing bandwidth/channel resources for these ports is handled by the
IOFireWireIsochChannelInterface.
Understanding NuDCL is essential for any application or driver that needs to perform high-performance, real-time audio or video streaming over FireWire on macOS.
Before NuDCL (introduced around macOS 10.2 "Jaguar"), the primary mechanism for building DCL programs was the IOFireWireDCLCommandPoolInterface. While functional, this older interface had limitations:
-
Less Type Safety: It used generic
DCLCommand*pointers, making it easier to misuse commands or pass incorrect structures. - More Limited API: Configuration options were often packed into fewer, less granular functions.
- Less Modern Design: It didn't integrate as cleanly with CoreFoundation paradigms.
NuDCL (IOFireWireNuDCLPoolInterface) represents a significant improvement:
-
Object-Oriented & Type-Safe: Uses distinct
NuDCLRefsubtypes (e.g.,NuDCLSendPacketRef,NuDCLReceivePacketRef) for different command types, improving code clarity and reducing errors. - Richer, More Granular API: Provides specific functions for setting and getting various attributes of DCL commands (buffers, branches, callbacks, status pointers, etc.).
-
CoreFoundation Integration: Leverages
CFSetRefandCFArrayReffor managing collections, such as update lists. - Explicit Scatter/Gather: Offers clearer functions for managing multi-buffer transfers.
While the underlying principles of DCL (linked lists, hardware abstraction, DMA compilation) remain similar, NuDCL provides a more robust, flexible, and developer-friendly API for implementing those principles in modern macOS FireWire development. This guide focuses exclusively on the NuDCL interface.
Before diving into the specifics of the NuDCL API (IOFireWireNuDCLPoolInterface), it's essential to understand the underlying concepts of FireWire isochronous transfers and the principles behind the Data Stream Control Language (DCL). NuDCL builds upon these foundations.
FireWire (IEEE 1394) provides two primary modes of data transfer: asynchronous and isochronous.
-
Asynchronous Transfer: This is similar to traditional packet-based protocols like USB or Ethernet. It guarantees data delivery through acknowledgements and retries but offers no guarantees about when the data will arrive or the consistency of the timing between packets. It's suitable for file transfers, control commands, and other data where precise timing isn't critical.
-
Isochronous Transfer: This mode is specifically designed for time-sensitive data, such as streaming digital audio and video. Its key characteristics are:
- Guaranteed Bandwidth: Devices negotiate and reserve a specific amount of the bus bandwidth for their isochronous streams before transmission begins. This ensures that once started, the stream has the necessary capacity.
- Low Latency & Predictable Timing: Data is transmitted in regularly scheduled intervals (typically every 125 microseconds, corresponding to a FireWire bus cycle). This minimizes jitter and ensures a smooth, continuous flow suitable for real-time applications.
- No Acknowledgements/Retries: To maintain timing guarantees, isochronous packets are typically not acknowledged, and there are no automatic retries at the protocol level if a packet is corrupted or dropped. Error handling must be managed by higher layers (e.g., by interpolating missing audio samples or dropping video frames).
- Channel-Based: Isochronous data is broadcast on specific "channels" (numbered 0-63). Devices listen only to the channels they are interested in.
NuDCL is the macOS mechanism for programming the hardware to participate in these precisely timed, high-bandwidth isochronous data exchanges.
DCL provides an abstraction layer for controlling the flow of isochronous data between system memory and the FireWire hardware's DMA engine. It allows developers to define complex data movement patterns without needing to know the specific register-level details of the underlying FireWire controller hardware.
A DCL program is conceptually a singly linked list of individual DCL commands. Each command represents a specific action to be performed during one or more FireWire bus cycles (e.g., send a packet, receive a packet, wait, jump to another command).
-
Execution Flow: By default, the hardware's DMA engine executes the commands sequentially, following the
nextpointer from one command to the next. - Control Flow: Special DCL commands (like branches in NuDCL) can alter this sequential flow, allowing for loops (essential for continuous streaming) or conditional execution paths.
-
Termination: The program ends when a command with a
NULLnext pointer (and no overriding branch) is reached.
NuDCL provides the API (IOFireWireNuDCLPoolInterface) to build these linked lists programmatically.
The core benefit of DCL/NuDCL is that it defines these data transfer operations in a hardware-independent manner. The developer creates the DCL program using the NuDCL API. The I/O Kit FireWire driver stack (specifically, the driver managing the FireWire host controller hardware, conceptually the role of the older FWIM) then takes this abstract DCL program and "compiles" or translates it into the specific sequence of DMA descriptors and control register writes required by the actual hardware chipset being used.
This abstraction shields the application or higher-level driver developer from the complexities and variations of different FireWire controller implementations.
Modern systems often manage memory in non-contiguous pages. Requiring data for a large isochronous packet to reside in a single, contiguous block of physical memory can be inefficient or impossible.
DCL/NuDCL directly supports scatter/gather DMA. This means:
- Scatter (Transmit): A single logical isochronous packet can be constructed ("gathered") from data residing in multiple, potentially non-contiguous, buffers in system memory. The NuDCL program specifies the list of these buffers for a single send command.
- Gather (Receive): An incoming isochronous packet can be split ("scattered") into multiple, potentially non-contiguous, buffers in system memory. The NuDCL program defines the destination buffer list for a single receive command.
This capability is crucial for performance, as it avoids costly memory-to-memory copies often required to consolidate data into a single buffer before DMA. NuDCL provides specific API calls (SetDCLRanges, AllocateSendPacket, AllocateReceivePacket) to define these scatter/gather lists.
Within the I/O Kit FireWire Family, NuDCL programs don't exist in isolation. They are associated with specific objects that manage their execution and resource allocation:
-
IOFireWireLocalIsochPortInterface: Represents an endpoint on the local machine for an isochronous stream. It can be either a talker (sending data) or a listener (receiving data). A NuDCL program is created using the NuDCL pool and then associated with a local isoch port. The port manages the execution context for its DCL program. -
IOFireWireIsochChannelInterface: Represents a specific FireWire isochronous channel (0-63) on a particular bus. It acts as a coordinator. One talker port and one or more listener ports (which can be local or represent remote devices) are added to a channel interface. The channel interface manages:- Negotiating and allocating the necessary bandwidth and the specific channel number with the bus Isochronous Resource Manager (IRM).
- Synchronizing the start and stop operations across all ports associated with the channel.
Therefore, the typical setup involves:
- Creating a NuDCL program using the pool.
- Creating a local isoch port and associating the NuDCL program with it.
- Creating an isochronous channel object.
- Adding the local port (as talker or listener) to the channel.
- (Optionally) Adding representations of remote ports if coordinating with other devices.
- Allocating resources and starting/stopping the stream via the channel interface.
The IOFireWireNuDCLPoolInterface is the central object for creating and managing NuDCL programs in the macOS I/O Kit FireWire Family. Think of it as the factory and container for your isochronous stream command lists.
The primary role of the NuDCL pool interface is to allocate and manage NuDCL command objects (NuDCLRef and its subtypes). When you use the allocation functions provided by this interface (like AllocateSendPacket, AllocateReceivePacket, AllocateSkipCycle), you are not just getting a command structure; you are adding that command to an internal linked list managed by the pool.
Key responsibilities and characteristics of the NuDCL pool:
- Command Allocation: It provides the functions to create individual NuDCL commands (send, receive, skip, etc.).
- Program Construction: As commands are allocated, the pool automatically links them together sequentially, forming the DCL program.
- Resource Management: The pool manages the memory associated with the NuDCL command structures it allocates.
-
Configuration Context: Some pool-level settings (like default Tag/Sync bits via
SetCurrentTagAndSync) influence subsequently allocated commands. -
Program Access: It provides methods (
GetProgram,GetDCLs) to retrieve the constructed DCL program for association with an isochronous port or for inspection. -
Debugging: Includes utility functions like
PrintProgramto help visualize the constructed command list.
You will interact extensively with the NuDCL pool interface whenever you need to define the sequence of operations for an isochronous data stream.
You don't create a NuDCL pool interface directly. Instead, you obtain it from an existing IOFireWireDevice user client interface. The IOFireWireDevice represents the FireWire device (often the local FireWire controller itself when setting up local ports) that will host the isochronous endpoint.
The process typically involves:
-
Get an
io_service_t: Find the I/O Kit service object for the desired FireWire device (e.g., using matching dictionaries andIOServiceGetMatchingServices). -
Create an
IOCFPlugInInterface: UseIOCreatePlugInInterfaceForServiceto create a generic plug-in interface for the service. -
Query for the
IOFireWireDeviceInterface: Use the plug-in interface'sQueryInterfacemethod, requesting the appropriateIOFireWireDeviceinterface UUID (e.g.,kIOFireWireDeviceInterfaceID_v3). This yields anIOFireWireLibDeviceRef. -
Query for the NuDCL Pool Interface: Use the
IOFireWireLibDeviceRef'sQueryInterfacemethod, requesting thekIOFireWireNuDCLPoolInterfaceIDUUID.
// Conceptual Example (Error handling omitted for brevity)
io_service_t firewireService;
IOCFPlugInInterface **plugInInterface = NULL;
IOFireWireLibDeviceRef deviceInterface = NULL;
IOFireWireLibNuDCLPoolRef nudclPoolInterface = NULL;
SInt32 score;
HRESULT hresult;
// Assume firewireService is obtained via matching
// Create Plug-in
hresult = IOCreatePlugInInterfaceForService(firewireService,
kIOFireWireLibTypeID,
kIOCFPlugInInterfaceID,
&plugInInterface,
&score);
if (hresult != S_OK || !plugInInterface) { /* Handle error */ }
// Query for Device Interface (use appropriate version)
hresult = (*plugInInterface)->QueryInterface(plugInInterface,
CFUUIDGetUUIDBytes(kIOFireWireDeviceInterfaceID_v3), // Or newer
(LPVOID)&deviceInterface);
// Release plug-in interface - no longer needed
(*plugInInterface)->Release(plugInInterface);
plugInInterface = NULL;
if (hresult != S_OK || !deviceInterface) { /* Handle error */ }
// Query for NuDCL Pool Interface
hresult = (*deviceInterface)->QueryInterface(deviceInterface,
CFUUIDGetUUIDBytes(kIOFireWireNuDCLPoolInterfaceID),
(LPVOID)&nudclPoolInterface);
if (hresult != S_OK || !nudclPoolInterface) {
// Handle error - NuDCL pool might not be supported or available
(*deviceInterface)->Release(deviceInterface); // Release device if pool fails
deviceInterface = NULL;
}
// --- If successful, nudclPoolInterface is now valid ---
// --- Later, when done ---
// if (nudclPoolInterface) { (*nudclPoolInterface)->Release(nudclPoolInterface); }
// if (deviceInterface) { (*deviceInterface)->Release(deviceInterface); }
// IOObjectRelease(firewireService);Once you have a valid IOFireWireLibNuDCLPoolRef, you can start allocating and configuring NuDCL commands.
The IOFireWireNuDCLPoolInterface, like other I/O Kit user client interfaces derived from IOCFPlugInInterface, follows a COM-style reference counting model for lifecycle management.
- Creation/Query: When you successfully obtain the interface via QueryInterface, its reference count is incremented.
- Release: You must call the interface's Release() method when you are finished using it. This decrements the reference count.
- Deallocation: When the reference count reaches zero, the underlying kernel object and associated resources (including all the NuDCL command structures allocated by that pool) are deallocated.
Crucial Point: Releasing the NuDCL pool interface automatically releases all the NuDCL commands (NuDCLRefs) that were allocated using that specific pool instance. You do not need to (and cannot) individually release NuDCLRefs.
Therefore, the lifetime of your NuDCL program is tied to the lifetime of the pool interface that created it. Ensure that any associated IOFireWireLocalIsochPortInterface has been stopped and released before you release the NuDCL pool interface it was using, otherwise, the port might attempt to access deallocated DCL commands, leading to crashes or undefined behavior.
Once you have obtained an IOFireWireNuDCLPoolInterface (as described in Chapter 3), you can begin constructing your Data Stream Control Language (DCL) program. This chapter outlines the general workflow and key techniques involved in using the NuDCL pool to build the command sequence for your isochronous stream.
Building a NuDCL program generally follows these steps:
-
Obtain the Pool: Get a valid
IOFireWireLibNuDCLPoolReffrom the targetIOFireWireLibDeviceRef. -
(Optional) Initialize Tracking: Create a
CFMutableSetRefif you plan to track specific DCLs for later updates (using thesaveBagparameter during allocation). -
(Optional) Set Defaults: If creating a send program, you might call
SetCurrentTagAndSyncon the pool to define default tag/sync values for subsequent send DCL allocations. -
Allocate Commands: Call the appropriate allocation functions on the NuDCL pool interface (
AllocateSendPacket,AllocateReceivePacket,AllocateSkipCycle, etc.) in the desired execution order. Pass thesaveBagset if tracking is needed. Each successful allocation appends the new command to the end of the program list being built within the pool. -
Configure Commands: After allocation (or sometimes integrated with more complex allocation patterns), use the various
Set*functions provided by the NuDCL pool interface to configure individual commands. This includes:- Setting branch targets (
SetDCLBranch) to create loops or specific sequences. - Defining scatter/gather buffer lists (
SetDCLRanges, etc.). - Assigning callbacks and user data (
SetDCLCallback,SetDCLRefcon). - Specifying update lists (
SetDCLUpdateList). - Setting timestamps or status pointers (
SetDCLTimeStampPtr,SetDCLStatusPtr). - Configuring send-specific parameters (tags, sync, user headers).
- Setting branch targets (
-
Retrieve Program: Once the program structure is complete, use
GetProgram()orGetDCLs()to get a reference to the constructed program, typically needed when associating it with anIOFireWireLocalIsochPortInterface. -
Associate with Port: Create an
IOFireWireLocalIsochPortInterfaceand associate the NuDCL program with it (details vary depending on the specific port creation method, which often implicitly takes the program start).
The NuDCL pool simplifies program construction by automatically linking commands as they are allocated.
- When you call an allocation function (e.g.,
AllocateSendPacket), the pool creates the new NuDCL command structure. - It then finds the last command currently in the pool's internal program list.
- It sets the
nextpointer (or equivalent internal link) of that last command to point to the newly allocated command. - The new command becomes the new "last" command in the list, with its
nextpointer initially set toNULL.
This means that simply calling allocation functions in the order you want the commands to execute (in the absence of branching) is sufficient to build the basic linear structure of the DCL program.
// Example: Allocating two commands links them automatically
NuDCLSendPacketRef dcl1, dcl2;
dcl1 = nudclPool->AllocateSendPacket(nudclPool, NULL, 1, &buffer1);
// Now dcl1 is the only command, dcl1->next is NULL (conceptually)
dcl2 = nudclPool->AllocateSendPacket(nudclPool, NULL, 1, &buffer2);
// Now dcl1->next points to dcl2, and dcl2->next is NULL (conceptually)Branch commands (SetDCLBranch) are used after allocation to override this default sequential linking and create non-linear program flow (like loops).
Many NuDCL allocation functions include an optional CFMutableSetRef saveBag parameter. This provides a convenient way to collect references (NuDCLRef) to specific DCL commands as they are created.
Purpose:
- Easy Access for Configuration: Instead of storing returned NuDCLRefs manually, you can add them to a set for easy iteration later when configuring branches, callbacks, or update lists.
- Building Update Lists: This is particularly useful when setting up update lists. DCLs that provide feedback (like receive commands or commands with timestamps/status pointers) often need to trigger updates on other DCLs (or themselves). You can allocate all relevant DCLs, adding them to a saveBag set, and then pass that set directly to SetDCLUpdateList.
Usage:
- Create a mutable set:
CFMutableSetRef mySet = CFSetCreateMutable(kCFAllocatorDefault, 0, NULL);(UsingNULLfor callbacks means the set will simply store theNuDCLRefpointers). - Pass
mySetas the saveBag argument during allocation:dcl = pool->AllocateReceivePacket(pool, mySet, ...); - The
dclreference will be added tomySet. - Later, you can use mySet (e.g., pass it to
SetDCLUpdateList). Remember thatSetDCLUpdateListconsumes a reference, so you might need toCFRetainthe set or create copies if using it multiple times. - Release the set when done:
CFRelease(mySet);
If you don't need to track a specific DCL this way, simply pass NULL for the saveBag parameter.
After allocating and configuring all the commands, you typically need a reference to the start of the program list to associate it with an IOFireWireLocalIsochPort.
-
DCLCommand* GetProgram( IOFireWireLibNuDCLPoolRef self ):
- This function returns a pointer to the very first
NuDCLcommand allocated using this pool instance. - Crucially, it returns a DCLCommand*, which is the older, less type-safe pointer type. This is provided primarily for backwards compatibility with APIs like
IOFireWireLibDeviceRef::CreateLocalIsochPortwhich were designed before NuDCL and expect the older type. - While you get a generic
DCLCommand*, the underlying object is still the specific NuDCL type you allocated (e.g.,NuDCLSendPacketRef).
- This function returns a pointer to the very first
-
CFArrayRef GetDCLs( IOFireWireLibNuDCLPoolRef self ):
- This function returns a
CFArrayRefcontaining all theNuDCLRefobjects allocated by the pool, likely in the order of allocation. - This provides a more modern, CoreFoundation-based way to access the entire program list.
- The caller is responsible for releasing the returned
CFArrayRefusingCFRelease.
- This function returns a
For most modern usage where you are associating the program with a port that understands NuDCL implicitly (or if the port creation mechanism has been updated), you might not need to explicitly call GetProgram. However, if interfacing with older APIs, GetProgram() is necessary. GetDCLs() is useful for introspection or managing the program structure using CoreFoundation.
NuDCL programs are constructed from different types of commands, each serving a specific purpose in managing the isochronous data stream. Unlike the older DCL system which used distinct opcodes within a generic structure, NuDCL uses specific typed references (e.g., NuDCLSendPacketRef, NuDCLReceivePacketRef, NuDCLSkipCycleRef) returned by dedicated allocation functions. This improves type safety and code clarity.
This chapter details the main categories of NuDCL commands and their core functionality. Configuration details (setting buffers, branches, callbacks, etc.) are covered more extensively in Chapter 6.
These commands are responsible for transmitting isochronous data packets onto the FireWire bus.
NuDCLSendPacketRef AllocateSendPacket(pool, saveBag, numBuffers, buffers)NuDCLSendPacketRef AllocateSendPacket_v(pool, saveBag, firstRange, ...)
These functions allocate a NuDCLSendPacketRef and append it to the program list within the pool.
- Purpose: To define a single isochronous packet to be sent during one bus cycle.
-
Buffers: You provide a list of one or more memory ranges (
IOVirtualRange) containing the packet's payload data using either an array (AllocateSendPacket) or varargs (AllocateSendPacket_v). This directly supports scatter/gather DMA for transmission. -
Return Value: Returns a typed
NuDCLSendPacketRefon success, orNULLon failure (e.g., pool is out of memory).
By default, the FireWire driver stack automatically generates the necessary 1394 isochronous packet header (containing data length, tag, channel, tcode, sy) based on the channel configuration and the total size of the data specified in the command's buffer list.
-
User Override: You can provide a custom header using
SetDCLUserHeaderPtr. This is an advanced feature, typically used only if you need precise control over header fields beyond what the system provides automatically. If a user header is provided, a mask must also be supplied, indicating which bits the system should still fill in (e.g., data length might still be calculated by the system). Changes to user headers often require an update notification (Notify) to take effect on running programs.
The isochronous header contains tag (2 bits) and sync (4 bits) fields, which can be used by applications for stream-level synchronization or packet classification.
- Defaults: When allocating send commands, the tag and sync bits default to 0.
-
Pool Default: You can set default tag/sync values for all subsequently allocated send commands in a pool using
IOFireWireNuDCLPoolInterface::SetCurrentTagAndSync. -
Per-DCL Override: You can override the default (or pool-set) values for an individual send command using
SetDCLTagBitsandSetDCLSyncBits.
These commands define how incoming isochronous data packets from the bus are received and placed into system memory.
NuDCLReceivePacketRef AllocateReceivePacket(pool, saveBag, headerBytes, numBuffers, buffers)NuDCLReceivePacketRef AllocateReceivePacket_v(pool, saveBag, headerBytes, firstRange, ...)
These functions allocate a NuDCLReceivePacketRef and append it to the program list.
- Purpose: To define the memory buffers where the payload (and optionally header) of a single incoming isochronous packet will be stored. One command typically consumes one incoming packet per bus cycle it's active.
-
Buffers: Similar to send commands, you provide a scatter/gather list of destination buffers (
IOVirtualRange). -
headerBytes: This parameter specifies how many bytes, if any, of the incoming packet's isochronous header should be prepended to the data payload stored in the buffers. Valid values are:-
0: Only the data payload is stored. -
4: The first quadlet (4 bytes) of the header is stored before the payload. -
8: Both quadlets (8 bytes) of the header are stored before the payload.
-
-
Return Value: Returns a typed
NuDCLReceivePacketRefon success, orNULLon failure.
If you specify headerBytes > 0, the header data will be written into the beginning of the buffer(s) you provided. Your buffer allocation must account for this extra space. Accessing the received header information requires an update notification (Notify) to be performed on the DCL command after it has executed and received the packet.
NuDCL achieves control flow not through dedicated jump/label command types, but by setting a branch target on any existing NuDCL command.
IOReturn SetDCLBranch(NuDCLRef dcl, NuDCLRef branchDCL)
This function modifies the execution flow after dcl completes.
-
Functionality: Instead of proceeding to the next command allocated sequentially, the DMA engine will jump to execute
branchDCLnext. -
Applies To: Any
NuDCLRef(Send, Receive, SkipCycle).
Loops are essential for continuous streaming. You create them by setting the branch target of the last command in the desired loop sequence to point back to the first command in the loop.
// Conceptual Example: Loop between recv1 and recv2
// ... allocate recv1, recv2 sequentially ...
pool->SetDCLBranch(recv1, recv2); // After recv1, go to recv2
pool->SetDCLBranch(recv2, recv1); // After recv2, go back to recv1Setting a command's branch target to NULL signifies the end of the program's execution path after that command completes.
- pool->SetDCLBranch(lastDCL, NULL);
If a command is reached that has no sequential next command (it was the last allocated) and no explicit branch set (its branch target is implicitly NULL), the program also terminates after that command executes.
NuDCL provides mechanisms to control timing and obtain synchronization information.
- NuDCLSkipCycleRef AllocateSkipCycle(pool)
This function allocates a NuDCLSkipCycleRef.
- Purpose: Instructs the DMA engine to effectively "do nothing" for one isochronous cycle slot associated with this channel. No packet is sent or received.
- Use Cases: Can be used for rate control (e.g., sending packets only every N cycles), inserting deliberate gaps in a stream, or potentially for resynchronization purposes.
As mentioned previously, timestamping is not a command type itself but a feature applied to other commands.
- Functionality: By calling SetDCLTimeStampPtr(dcl, pointer), you request that the system record the FireWire cycle time counter value when dcl executes.
- Retrieval: The timestamp is written to the memory location specified by pointer, but only after an update notification (Notify) is processed for dcl.
Similar to timestamping, callbacks, status reporting, and user reference constants are not distinct command types but rather properties or features that can be associated with any NuDCL command (NuDCLRef).
- Callbacks (SetDCLCallback): Allow your code to be notified when a specific DCL command executes. Useful for processing received data, queueing new send buffers, or monitoring progress.
- Status Reporting (SetDCLStatusPtr): Provides a mechanism to retrieve hardware-level status (e.g., success, ack error, data CRC error, FIFO overrun) associated with the execution of a specific DCL command. Requires an update (Notify).
- User Reference Constants (SetDCLRefcon): Allow you to associate arbitrary application-specific data (as a void*) with any DCL command. This refcon is often retrieved within the DCL's callback to provide context (e.g., which buffer corresponds to this callback).
The specific API functions for setting and getting these properties are detailed in the next chapter.
Chapter 5 introduced the different types of NuDCL commands. This chapter delves into the specific API functions provided by the IOFireWireNuDCLPoolInterface used to configure the behavior and properties of these commands after they have been allocated.
Remember that changes made using these Set* functions generally apply immediately to DCL programs that haven't started running yet. To modify a program that is currently running (associated with an active isochronous port), you must follow the modification with a call to IOFireWireLocalIsochPortInterface::Notify(), as detailed in Chapter 7.
These functions can be used to configure properties applicable to any type of NuDCL command (NuDCLRef, including send, receive, and skip types).
These functions control the execution flow between DCL commands.
-
IOReturn SetDCLBranch(NuDCLRef dcl, NuDCLRef branchDCL)-
Purpose: Sets the next command to execute after
dcl. Overrides the default sequential linking. -
dcl: The command whose branch target is being set. -
branchDCL: The command to jump to next. IfNULL, the program path terminates afterdcl. - Notes: Essential for creating loops and non-linear program flow.
-
Purpose: Sets the next command to execute after
-
NuDCLRef GetDCLBranch(NuDCLRef dcl)-
Purpose: Retrieves the currently set branch target for
dcl. -
dcl: The command to query. -
Return: The
NuDCLRefof the branch target, orNULLif no branch is set (or if it branches to termination).
-
Purpose: Retrieves the currently set branch target for
-
NuDCLRef FindDCLNextDCL(IOFireWireLibNuDCLPoolRef self, NuDCLRef dcl)-
Purpose: Finds the command that was allocated immediately after
dclin the pool's sequence. -
self: The NuDCL pool interface. -
dcl: The command whose sequential successor is desired. -
Return: The
NuDCLRefof the next allocated command, orNULLifdclwas the last one allocated. -
Notes: Useful for understanding the allocation order, independent of any explicit branching set via
SetDCLBranch.
-
Purpose: Finds the command that was allocated immediately after
These allow associating application logic and data with specific points in the DCL program execution.
-
IOReturn SetDCLCallback(NuDCLRef dcl, NuDCLCallback callback)-
Purpose: Registers a C function pointer (
NuDCLCallback) to be called whendclexecutes. -
dcl: The command to associate the callback with. -
callback: The function to call. The function signature is typicallyvoid (*NuDCLCallback)(NuDCLRef dcl, void* refcon). - Notes: If the DCL also has an update list, the callback might occur after the update completes.
-
Purpose: Registers a C function pointer (
-
NuDCLCallback GetDCLCallback(NuDCLRef dcl)-
Purpose: Retrieves the callback function associated with
dcl. -
dcl: The command to query. -
Return: The function pointer, or
NULLif none is set.
-
Purpose: Retrieves the callback function associated with
-
void SetDCLRefcon(NuDCLRef dcl, void* refcon)-
Purpose: Associates an arbitrary user-defined pointer (
void*) withdcl. -
dcl: The command to associate the data with. -
refcon: The user data pointer. -
Notes: This
refconis typically passed as the second argument to theNuDCLCallbackfunction, providing context (e.g., identifying the buffer related to the callback).
-
Purpose: Associates an arbitrary user-defined pointer (
-
void* GetDCLRefcon(NuDCLRef dcl)-
Purpose: Retrieves the user reference constant associated with
dcl. -
dcl: The command to query. -
Return: The user data pointer, or
NULLif none was set.
-
Purpose: Retrieves the user reference constant associated with
Enables recording the FireWire bus cycle time when a command executes.
-
IOReturn SetDCLTimeStampPtr(NuDCLRef dcl, UInt32* timeStampPtr)-
Purpose: Designates a memory location where the 32-bit cycle timer value should be written upon execution of
dcl. -
dcl: The command to enable timestamping for. -
timeStampPtr: A valid pointer to aUInt32variable in application memory. -
Notes: The value at
timeStampPtris not valid until an update notification (Notify) has been processed fordclafter it has executed.
-
Purpose: Designates a memory location where the 32-bit cycle timer value should be written upon execution of
-
UInt32* GetDCLTimeStampPtr(NuDCLRef dcl)-
Purpose: Retrieves the pointer previously set by
SetDCLTimeStampPtr. -
dcl: The command to query. -
Return: The pointer, or
NULLif timestamping is not enabled fordcl.
-
Purpose: Retrieves the pointer previously set by
Allows retrieving hardware status codes related to a command's execution.
-
IOReturn SetDCLStatusPtr(NuDCLRef dcl, UInt32* statusPtr)-
Purpose: Designates a memory location where the hardware execution status code should be written upon execution of
dcl. -
dcl: The command to enable status reporting for. -
statusPtr: A valid pointer to aUInt32variable in application memory. -
Notes: Status codes indicate success (
ack_complete) or various errors (overrun, timeout, CRC error, etc. - see header for codes). The value atstatusPtris not valid until an update notification (Notify) has been processed fordclafter it has executed.
-
Purpose: Designates a memory location where the hardware execution status code should be written upon execution of
-
UInt32* GetDCLStatusPtr(NuDCLRef dcl)-
Purpose: Retrieves the pointer previously set by
SetDCLStatusPtr. -
dcl: The command to query. -
Return: The pointer, or
NULLif status reporting is not enabled fordcl.
-
Purpose: Retrieves the pointer previously set by
Update lists specify which other DCL commands should have their status (including timestamps, completion status, potentially received data offsets/lengths) refreshed from the hardware/driver state after a particular command (dcl) executes. This is the primary mechanism for getting feedback from the asynchronous DMA process.
-
IOReturn SetDCLUpdateList(NuDCLRef dcl, CFSetRef dclList)-
Purpose: Sets the entire list of DCLs (
NuDCLRefs contained in theCFSetRef) that should be updated afterdclexecutes. -
dcl: The command whose execution triggers the updates. -
dclList: ACFSetRefcontaining theNuDCLRefs to be updated. The pool interface consumes one reference count of this set. -
Notes: Often used with sets created using the
saveBagparameter during allocation. Essential for receive operations and status/timestamp checking.
-
Purpose: Sets the entire list of DCLs (
-
IOReturn AppendDCLUpdateList(NuDCLRef dcl, NuDCLRef updateDCL)-
Purpose: Adds a single
NuDCLRef(updateDCL) to the existing update list ofdcl. -
dcl: The command whose execution triggers the update. -
updateDCL: The command to add to the update list.
-
Purpose: Adds a single
-
CFSetRef CopyDCLUpdateList(NuDCLRef dcl)-
Purpose: Retrieves a copy of the update list associated with
dcl. -
dcl: The command to query. -
Return: A new
CFSetRefcontaining theNuDCLRefs in the update list. The caller owns this returned set and mustCFReleaseit. ReturnsNULLif no list is set.
-
Purpose: Retrieves a copy of the update list associated with
-
IOReturn RemoveDCLUpdateList(NuDCLRef dcl)-
Purpose: Clears the update list associated with
dcl. -
dcl: The command whose update list is to be removed.
-
Purpose: Clears the update list associated with
These provide more generic control, though specific flag meanings are not defined in the header comments.
-
void SetDCLFlags(NuDCLRef dcl, UInt32 flags)/UInt32 GetDCLFlags(NuDCLRef dcl)- Purpose: Set or get generic flags associated with a DCL command. Consult specific driver or hardware documentation if available for flag definitions.
-
IOReturn SetDCLWaitControl(NuDCLRef dcl, Boolean wait)- Purpose: Unknown from header comments. Might relate to waiting for command completion or specific hardware synchronization, but this is speculative.
These functions configure properties specific to commands that transfer data payloads (NuDCLSendPacketRef and NuDCLReceivePacketRef).
These functions manage the scatter/gather lists associated with send and receive commands.
-
IOReturn SetDCLRanges(NuDCLRef dcl, UInt32 numRanges, IOVirtualRange* ranges)/IOReturn SetDCLRanges_v(NuDCLRef dcl, IOVirtualRange* firstRange, ...)-
Purpose: Defines the complete list of virtual memory buffers used by
dclfor data transfer. Overwrites any previously set ranges. -
dcl: The send or receive command to configure. -
numRanges/rangesorfirstRange, ...: The scatter/gather list. - Notes: Critical for defining where data comes from (send) or goes to (receive).
-
Purpose: Defines the complete list of virtual memory buffers used by
-
IOReturn AppendDCLRanges(NuDCLRef dcl, UInt32 numRanges, IOVirtualRange* range)-
Purpose: Adds one or more buffer ranges to the end of the existing scatter/gather list for
dcl. -
dcl: The send or receive command to modify. -
numRanges/range: The range(s) to append.
-
Purpose: Adds one or more buffer ranges to the end of the existing scatter/gather list for
-
UInt32 GetDCLRanges(NuDCLRef dcl, UInt32 maxRanges, IOVirtualRange* outRanges)-
Purpose: Retrieves the list of buffer ranges associated with
dcl. -
dcl: The command to query. -
maxRanges: The maximum number of ranges theoutRangesbuffer can hold. -
outRanges: A caller-provided buffer to be filled with theIOVirtualRangestructures. -
Return: The actual number of ranges copied into
outRanges.
-
Purpose: Retrieves the list of buffer ranges associated with
-
UInt32 CountDCLRanges(NuDCLRef dcl)-
Purpose: Returns the number of separate buffer ranges currently defined for
dcl. -
dcl: The command to query.
-
Purpose: Returns the number of separate buffer ranges currently defined for
-
IOByteCount GetDCLSize(NuDCLRef dcl)-
Purpose: Calculates and returns the total size in bytes of all buffer ranges combined for
dcl. -
dcl: The command to query.
-
Purpose: Calculates and returns the total size in bytes of all buffer ranges combined for
-
IOReturn GetDCLSpan(NuDCLRef dcl, IOVirtualRange* spanRange)-
Purpose: Determines the single virtual memory range that encompasses all buffers defined for
dcl, from the lowest starting address to the highest ending address. -
dcl: The command to query. -
spanRange: A caller-providedIOVirtualRangestructure to be filled with the result. - Notes: Useful for understanding the total memory footprint touched by the DCL, even if it's non-contiguous.
-
Purpose: Determines the single virtual memory range that encompasses all buffers defined for
These functions apply only to NuDCLSendPacketRef commands.
Control the tag and sync bits embedded in the outgoing isochronous packet header.
-
void SetCurrentTagAndSync(IOFireWireLibNuDCLPoolRef self, UInt8 tag, UInt8 sync)-
Purpose: Sets the default tag and sync values for send commands allocated after this call within the specified
pool. -
self: The NuDCL pool interface. -
tag: Default tag value (0-3). -
sync: Default sync value (0-15).
-
Purpose: Sets the default tag and sync values for send commands allocated after this call within the specified
-
IOReturn SetDCLTagBits(NuDCLRef dcl, UInt8 tagBits)/UInt8 GetDCLTagBits(NuDCLRef dcl)-
Purpose: Set or get the specific 2-bit tag value for an individual send command
dcl, overriding any pool default.
-
Purpose: Set or get the specific 2-bit tag value for an individual send command
-
IOReturn SetDCLSyncBits(NuDCLRef dcl, UInt8 syncBits)/UInt8 GetDCLSyncBits(NuDCLRef dcl)-
Purpose: Set or get the specific 4-bit sync value for an individual send command
dcl, overriding any pool default.
-
Purpose: Set or get the specific 4-bit sync value for an individual send command
Allow providing a custom isochronous header instead of relying solely on system generation.
-
IOReturn SetDCLUserHeaderPtr(NuDCLRef dcl, UInt32 * headerPtr, UInt32 * mask)-
Purpose: Specifies a custom 8-byte header (
headerPtr) and a corresponding mask (mask). The system will use the bits fromheaderPtrwhere themaskhas a '1', and generate the bits where themaskhas a '0'. -
dcl: The send command to configure. -
headerPtr: Pointer to the 8-byte user-defined header data. -
mask: Pointer to the 8-byte mask. -
Notes: Advanced use. Requires an update notification (
Notify) for changes to apply to running programs.
-
Purpose: Specifies a custom 8-byte header (
-
UInt32* GetDCLUserHeaderPtr(NuDCLRef dcl)- Purpose: Retrieves the pointer to the user-defined header data.
-
UInt32* GetUserHeaderMaskPtr(NuDCLRef dcl)- Purpose: Retrieves the pointer to the user-defined header mask.
Chapters 4, 5, and 6 described how to build and configure a NuDCL program using the IOFireWireNuDCLPoolInterface. This chapter explains how to execute that program and, crucially, how to modify it while it's running – a key feature for continuous isochronous streaming applications.
A NuDCL program, once constructed within its pool, doesn't execute on its own. It must be associated with an IOFireWireLocalIsochPortInterface. This interface represents the actual hardware endpoint (either a talker sending data or a listener receiving data) on the local Mac.
The association typically happens during the creation of the IOFireWireLocalIsochPortInterface. You obtain the IOFireWireLibDeviceRef for the FireWire controller, and then call a method like CreateLocalIsochPort (or a similar method depending on the specific version of the IOFireWireDevice interface you are using). This creation method often takes the starting command of the DCL program (obtained via IOFireWireNuDCLPoolInterface::GetProgram()) as a parameter.
Once associated:
- The
IOFireWireLocalIsochPortInterfaceholds a reference to the NuDCL program. - The underlying driver stack prepares the hardware based on the DCL program's structure (often involving "compiling" it into hardware-specific DMA descriptors).
- The port is ready to be started as part of an isochronous channel.
The execution of the NuDCL program associated with a local port is controlled indirectly via the IOFireWireIsochChannelInterface to which the port is added.
-
AllocateChannel(): Prepares all ports (talker and listeners) associated with the channel, typically involving resource allocation and potentially final hardware setup based on the compiled DCL. -
Start(): Begins the isochronous data transfer for all ports on the channel. For a local port, this initiates the execution of its associated NuDCL program by the hardware's DMA engine. The engine starts processing DCL commands according to the defined sequence and branches. -
Stop(): Halts the isochronous data transfer for all ports on the channel. For a local port, this stops the hardware DMA engine from processing further NuDCL commands. The program might stop mid-execution. -
ReleaseChannel(): Releases resources associated with the channel and its ports after they have been stopped.
Directly starting or stopping the NuDCL program itself isn't done; control flows through the channel that manages the overall stream synchronization.
For many applications, especially continuous streaming, the NuDCL program cannot be entirely static. Common scenarios requiring dynamic modification include:
- Buffer Recycling (Receive): When receiving data, buffers fill up. The application needs to process the data in a full buffer and then make that buffer available again for receiving new data, often by redirecting a DCL command's branch or updating its buffer list.
- Buffer Provisioning (Send): When sending data, the application generates new data continuously. It needs to update DCL commands to point to buffers containing the next chunk of data to be transmitted.
-
Flow Control Changes: An application might need to change the looping behavior or insert
SkipCyclecommands based on external conditions. - Error Handling: Based on status feedback, the application might need to alter the program flow to handle errors.
NuDCL is designed to accommodate these changes while the stream is active.
Simply changing a NuDCL command's properties using the pool interface functions (like SetDCLRanges or SetDCLBranch) is not sufficient to affect a running DCL program. The hardware DMA engine is likely operating on a compiled, cached version of the program structure.
To make changes take effect, you must explicitly inform the associated IOFireWireLocalIsochPortInterface about the modifications using its Notify method:
-
IOReturn Notify(IOFireWireLibLocalIsochPortRef self, IOFWDCLNotificationType notificationType, void ** inDCLList, UInt32 numDCLs)-
self: The local isoch port interface whose DCL program is being modified/updated. -
notificationType: Specifies the reason for the notification:-
kFWDCLModifyNotification: Used when you have changed the structure or parameters of one or more DCLs (e.g., changed a branch target, updated buffer pointers/sizes, changed callbacks, set user headers). This signals the driver that parts of the DCL program may need recompilation or direct hardware updates. -
kFWDCLUpdateNotification: Used when you need the driver to refresh the status (timestamps, hardware status codes, potentially buffer offsets/transfer counts) of one or more DCLs from the hardware to the memory locations specified bySetDCLTimeStampPtr,SetDCLStatusPtr, etc. This is how you retrieve feedback after a DCL has executed.
-
-
inDCLList: An array of pointers to the DCL commands that have been modified or need updating. Important: The API expectsvoid **. While you will likely haveNuDCLReftypes, you will need to cast them appropriately when creating this list (e.g., create an arrayNuDCLRef modifiedDCLs[] = {dcl1, dcl2};and pass(void**)modifiedDCLs). Be cautious with pointer types and sizes. -
numDCLs: The number of DCL pointers ininDCLList.
-
Workflow for Dynamic Modification:
- Use
IOFireWireNuDCLPoolInterfacefunctions (e.g.,SetDCLRanges,SetDCLBranch) to modify the desiredNuDCLRef(s) in memory. - Create an array containing pointers to the modified
NuDCLRef(s). - Call the
Notifymethod on the associatedIOFireWireLocalIsochPortInterface, passingkFWDCLModifyNotificationand the array of modified DCLs.
Workflow for Getting Updates (Status/Timestamp):
- Ensure
SetDCLTimeStampPtrand/orSetDCLStatusPtrwere called for the relevant DCLs during setup. - Often, within a callback triggered by a preceding DCL's execution, or based on other timing, identify the DCLs whose status you need.
- Create an array containing pointers to these
NuDCLRef(s). - Call the
Notifymethod on the associatedIOFireWireLocalIsochPortInterface, passingkFWDCLUpdateNotificationand the array of DCLs to be updated. -
After the
Notifycall returns successfully, the memory locations pointed to by the status/timestamp pointers associated with the updated DCLs should contain the refreshed values from the hardware/driver.
Callbacks (NuDCLCallback), set using SetDCLCallback, are a cornerstone of managing active streams. They are invoked by the driver when the corresponding DCL command execution point is reached (or potentially just after its update list is processed).
Common uses for callbacks:
- Receive Completion: A callback on a receive DCL signals that data has arrived in the associated buffer. The callback handler can then process this data (e.g., copy it to application buffers, signal other threads).
-
Buffer Management: The callback can trigger the dynamic modification process. For example:
- A receive callback, after processing data, might call
SetDCLRangesto point the same DCL command to a new empty buffer and then callNotifywithkFWDCLModifyNotification. - A send callback (perhaps placed after a block of send DCLs using an
UpdateDCLList) could signal that buffers are now free, allowing the application to fill them with new data and update subsequent send DCLs viaSetDCLRangesandNotify.
- A receive callback, after processing data, might call
- Progress Monitoring: Callbacks can be used simply to track how far the DCL program execution has progressed.
Callbacks execute in the driver's context (often a dedicated thread or run loop source associated with the isochronous port/channel). They should be efficient and avoid blocking operations. Complex processing should typically be offloaded to application threads, with the callback merely signaling completion or data availability.
Properly cleaning up resources is vital to avoid leaks and deadlocks.
-
Stopping the Stream: Always
Stop()theIOFireWireIsochChannelInterfacebefore attempting to release resources. -
Pending Operations: When
Stop()is called, there might still be DCL commands in flight, or callbacks/updates pending execution in the driver's context. Releasing resources prematurely can cause crashes. -
Finalize Callback: The
IOFireWireLocalIsochPortInterfaceprovidesSetFinalizeCallback. This callback is invoked after the port has been stopped and all pending operations (including DCL execution, updates, and user-set callbacks likeNuDCLCallback) associated with that port have completed. -
Safe Cleanup Sequence:
- Call
Stop()on theIOFireWireIsochChannelInterface. - (Optional but recommended) Set a finalize callback on the
IOFireWireLocalIsochPortInterfaceusingSetFinalizeCallback. - Wait for the finalize callback to be invoked. This confirms the port is truly idle.
- Call
ReleaseChannel()on theIOFireWireIsochChannelInterface. - Call
Release()on theIOFireWireLocalIsochPortInterface. - Call
Release()on theIOFireWireNuDCLPoolInterface(this frees the DCL program memory). - Deallocate application buffers associated with the stream.
- Call
Using the finalize callback ensures that you don't release the NuDCL pool or application buffers while the port or driver might still be trying to access them.
This chapter illustrates common patterns for using the NuDCL API to build isochronous streaming programs. These examples focus on the core NuDCL logic. Full application code would also require obtaining the IOFireWireLibDeviceRef, creating the IOFireWireLocalIsochPortInterface and IOFireWireIsochChannelInterface, handling bandwidth/channel allocation, starting/stopping the channel, and comprehensive error checking and resource management (including Release() calls).
Assume nudclPool is a valid IOFireWireLibNuDCLPoolRef obtained as shown in Chapter 3.
Goal: Continuously transmit data, alternating between two pre-filled buffers (bufferA and bufferB).
Concept: Create two NuDCLSendPacket commands. Set the branch of the first to point to the second, and the branch of the second to point back to the first, creating a simple ping-pong loop.
// --- Assume bufferA and bufferB are IOVirtualRanges pointing to valid data ---
IOVirtualRange bufferA = { .address = addressOfBufferA, .length = kPacketSize };
IOVirtualRange bufferB = { .address = addressOfBufferB, .length = kPacketSize };
NuDCLSendPacketRef sendDCL_A = NULL;
NuDCLSendPacketRef sendDCL_B = NULL;
DCLCommand* programStart = NULL;
IOReturn kr;
// 1. Allocate the first send command
sendDCL_A = (*nudclPool)->AllocateSendPacket(nudclPool,
NULL, // Not saving in a bag for this simple example
1, // One buffer range
&bufferA);
if (!sendDCL_A) { /* Handle error */ }
// 2. Allocate the second send command
sendDCL_B = (*nudclPool)->AllocateSendPacket(nudclPool,
NULL,
1,
&bufferB);
if (!sendDCL_B) { /* Handle error */ }
// 3. Create the loop by setting branches
kr = (*nudclPool)->SetDCLBranch((NuDCLRef)sendDCL_A, (NuDCLRef)sendDCL_B);
if (kr != kIOReturnSuccess) { /* Handle error */ }
kr = (*nudclPool)->SetDCLBranch((NuDCLRef)sendDCL_B, (NuDCLRef)sendDCL_A);
if (kr != kIOReturnSuccess) { /* Handle error */ }
// 4. Get the program start (for associating with port)
programStart = (*nudclPool)->GetProgram(nudclPool); // Should point to sendDCL_A
// --- Now associate 'programStart' with a local isoch port (talker) ---
// --- Add port to channel, allocate, and start channel ---Notes:
- This example assumes bufferA and bufferB are static. For continuous streaming with changing data, you would need dynamic updates (see Scenario 2 concept applied to sending).
- No callbacks or update lists are strictly needed if you don't require feedback on send completion.
Goal: Continuously receive isochronous data into alternating buffers (bufferA, bufferB), process the data in a callback, and potentially reuse the buffers.
Concept: Similar structure to the send loop, but uses NuDCLReceivePacket. Callbacks are set on each receive DCL. The callback processes the buffer (identified via refcon) and could signal another thread. To reuse buffers, the callback would need to modify the other DCL command's buffer pointer (SetDCLRanges) and trigger a Notify on the port – this is more advanced and not fully shown here, focusing instead on the callback notification aspect. Update lists are used to refresh DCL status if needed.
IOVirtualRange sendChunks[3];
NuDCLSendPacketRef scatterSendDCL = NULL;
// Assume chunk1Ptr, chunk2Ptr, chunk3Ptr point to valid data
sendChunks[0].address = chunk1Ptr;
sendChunks[0].length = kChunk1Size;
sendChunks[1].address = chunk2Ptr;
sendChunks[1].length = kChunk2Size;
sendChunks[2].address = chunk3Ptr;
sendChunks[2].length = kChunk3Size;
// Allocate ONE DCL command for all chunks
scatterSendDCL = (*nudclPool)->AllocateSendPacket(nudclPool,
NULL,
3, // Number of ranges in the array
sendChunks); // Pass the array
if (!scatterSendDCL) { /* Handle error */ }
// --- Configure branches, callbacks etc. as needed ---
// --- Associate with port and channel ---Notes:
- The FireWire hardware/driver gathers the data from chunk1, chunk2, and chunk3 in order to form the single isochronous packet on the bus.
- The same principle applies to receiving (AllocateReceivePacket) where incoming data would be scattered into the specified buffer list.
Goal: After sending a packet (sendDCL), get the execution status and the exact cycle timestamp.
Concept: Allocate the sendDCL. Allocate memory for status and timestamp variables. Configure sendDCL using SetDCLStatusPtr and SetDCLTimeStampPtr. Create a CFMutableSetRef containing only sendDCL. Configure sendDCL (or a subsequent DCL) to trigger an update of this set using SetDCLUpdateList. Set a callback on the triggering DCL. Inside the callback, the status and timestamp values can be read.
NuDCLSendPacketRef sendDCL = NULL;
NuDCLRef triggerDCL = NULL; // Could be sendDCL or a later one
UInt32 dclStatus = 0;
UInt32 dclTimestamp = 0;
CFMutableSetRef updateSet = NULL;
IOReturn kr;
IOVirtualRange sendBuffer = { /* ... */ };
// --- Callback function ---
void SendCompletionCallback(NuDCLRef executedDCL, void* refcon) {
// This callback runs *after* triggerDCL executed AND
// the DCLs in its update list (sendDCL in this case) have been updated.
printf("Send DCL completed.\n");
printf(" Hardware Status: 0x%08X\n", dclStatus);
printf(" Timestamp: 0x%08X\n", dclTimestamp);
// Check dclStatus for errors (ack_complete means success)
}
// --- Main setup ---
// 1. Allocate the send DCL
sendDCL = (*nudclPool)->AllocateSendPacket(nudclPool, NULL, 1, &sendBuffer);
if (!sendDCL) { /* Handle error */ }
// 2. Set status and timestamp pointers for sendDCL
kr = (*nudclPool)->SetDCLStatusPtr((NuDCLRef)sendDCL, &dclStatus);
kr = (*nudclPool)->SetDCLTimeStampPtr((NuDCLRef)sendDCL, &dclTimestamp);
// 3. Create the update set containing sendDCL
updateSet = CFSetCreateMutable(kCFAllocatorDefault, 0, NULL);
if (!updateSet) { /* Handle error */ }
CFSetAddValue(updateSet, (const void*)sendDCL);
// 4. Set the update list on the trigger DCL.
// For simplicity, let's make sendDCL trigger its own update.
triggerDCL = (NuDCLRef)sendDCL;
kr = (*nudclPool)->SetDCLUpdateList(triggerDCL, updateSet); // Pool consumes set reference
if (kr != kIOReturnSuccess) { /* Handle error */ }
updateSet = NULL; // Avoid dangling pointer
// 5. Set the callback on the trigger DCL
kr = (*nudclPool)->SetDCLCallback(triggerDCL, SendCompletionCallback);
if (kr != kIOReturnSuccess) { /* Handle error */ }
// --- Configure branches (e.g., loop or terminate) ---
// --- Associate with port and channel ---
// --- Remember CFRelease(updateSet) if allocated but not consumed ---Notes:
- The key is that the updateSet tells the system which DCLs need their status/timestamp memory updated when the trigger DCL executes.
- The callback on the trigger DCL is a convenient place to read these updated values, as the update mechanism completes just before the callback runs.
Notes:
- The key is that the updateSet tells the system which DCLs need their status/timestamp memory updated when the trigger DCL executes.
- The callback on the trigger DCL is a convenient place to read these updated values, as the update mechanism completes just before the callback runs.
While this guide focuses on the IOFireWireNuDCLPoolInterface for building isochronous data stream programs, understanding how NuDCL interacts with other key interfaces within the I/O Kit FireWire Family is essential for successful implementation. This chapter provides a brief overview of the most relevant interfaces.
-
Role: Represents a FireWire device in the I/O Kit registry, discovered and managed by the
IOFireWireFamily.kext. This could be a peripheral device or, importantly for local isochronous operations, the Mac's own FireWire host controller interface. -
Obtaining: Typically found using I/O Kit matching functions (e.g.,
IOServiceGetMatchingServiceswith appropriate matching dictionaries). -
Interaction with NuDCL: The
IOFireWireDeviceuser client interface (specifically, anIOFireWireLibDeviceRefobtained viaQueryInterface) is the factory for theIOFireWireNuDCLPoolInterface. You must obtain the device interface first and then query it to get the NuDCL pool interface needed to start building your DCL program. - Other Functions: Provides access to many other FireWire functionalities like asynchronous transactions, bus resets, topology information, and configuration ROM access, as well as being the factory for isochronous port interfaces.
- Role: Represents a local endpoint (on the Mac running the code) for an isochronous data stream. It acts as either a talker (sending data) or a listener (receiving data).
-
Obtaining: Created via methods on the
IOFireWireLibDeviceRef(e.g.,CreateLocalIsochPort), typically requiring the startingDCLCommand*of the NuDCL program (obtained viaIOFireWireNuDCLPoolInterface::GetProgram()) during creation. -
Interaction with NuDCL:
- Association: This interface is directly associated with a specific NuDCL program during its creation. It holds the context for executing that program.
-
Dynamic Modification/Updates: The crucial
Notify()method resides on this interface. You callNotify()on the port to signal that NuDCL commands (managed by the pool) have been modified in memory (kFWDCLModifyNotification) or that their status/timestamp needs to be refreshed from the hardware (kFWDCLUpdateNotification). -
Lifecycle: Needs to be properly stopped (via the channel) and released (
Release()). Provides theSetFinalizeCallback()method to ensure all operations are complete before final cleanup.
- Role: Represents and manages a specific isochronous channel (0-63) on a FireWire bus. It acts as the primary coordinator for an isochronous stream.
-
Obtaining: Created via methods on the
IOFireWireLibDeviceRef(e.g.,CreateIsochChannel). -
Interaction with NuDCL (Indirect): While it doesn't directly interact with NuDCL commands, it's essential for managing the port that does execute the NuDCL program.
-
Port Management: You add your
IOFireWireLocalIsochPortInterface(and potentially representations of remote ports) to the channel usingSetTalker()orAddListener(). - Resource Management: Responsible for interacting with the bus Isochronous Resource Manager (IRM) to allocate the required bandwidth and a specific channel number for the stream.
-
Execution Control: Provides the
AllocateChannel(),Start(),Stop(), andReleaseChannel()methods which synchronize the setup, execution, and teardown across all ports associated with that channel. CallingStart()on the channel effectively starts the execution of the NuDCL program(s) associated with its port(s).
-
Port Management: You add your
In summary, you use the IOFireWireDevice to get the IOFireWireNuDCLPoolInterface. You use the pool to build the NuDCL program. You associate this program with an IOFireWireLocalIsochPortInterface (also obtained via the device). You add the port to an IOFireWireIsochChannelInterface (obtained via the device). You control the stream execution via the channel, and you notify the port about dynamic changes or update requests related to the NuDCL program.
Working with low-level, high-performance interfaces like NuDCL for FireWire isochronous streaming can present unique challenges. This chapter provides guidance on using the available debugging tools and discusses common problems developers might encounter.
The IOFireWireNuDCLPoolInterface includes utility functions designed specifically to help visualize the structure and configuration of your NuDCL program:
-
void PrintProgram(IOFireWireLibNuDCLPoolRef self)-
Purpose: Dumps a textual representation of the entire DCL program currently constructed within the specified pool to the system log (viewable via Console.app or
syslog). -
When to Use: Call this after you believe you have finished allocating and configuring all DCL commands in the pool, but before associating the program with a port or starting the channel. It helps verify:
- The sequential linking of commands.
- Explicitly set branch targets.
- The types of commands allocated.
- Basic configuration details (like buffer addresses/sizes visible at that time).
- Output: The exact format depends on the OS version, but it typically lists each DCL in sequence, showing its type, memory address, and key parameters like buffer pointers, sizes, and branch targets.
-
Purpose: Dumps a textual representation of the entire DCL program currently constructed within the specified pool to the system log (viewable via Console.app or
-
void PrintDCL(NuDCLRef dcl)-
Purpose: Dumps a textual representation of a single specified NuDCL command (
dcl) to the system log. -
When to Use: Useful for inspecting the detailed configuration of a specific command, perhaps during dynamic modification or within a callback, to verify its current state (buffer pointers, flags, update list status, etc.). Note that dynamically updated fields like status/timestamps read from hardware require a
Notifycall before their in-memory representation (and thus thePrintDCLoutput) reflects the hardware state. -
Output: Provides a detailed breakdown of the specified
NuDCLRef's properties.
-
Purpose: Dumps a textual representation of a single specified NuDCL command (
Using these functions systematically during development can save significant time by revealing structural errors or incorrect configurations in your DCL program logic before attempting to execute it.
When you enable status reporting using SetDCLStatusPtr, the UInt32 value written to your status variable after an update notification provides direct feedback from the hardware/driver about the execution outcome of that specific DCL command. Interpreting these codes is crucial for error handling.
Common status codes (based on OHCI specification, values may vary slightly):
-
ack_complete(e.g., 0x11): Success. The DCL command executed without detected hardware errors. -
ack_pending(e.g., 0x02 in some contexts, though less common for pure isoch status): The operation was initiated but completion is pending (more relevant to asynchronous operations, but might appear). -
evt_long_packet(Receive, e.g., 0x02): Received packet data length was greater than the buffer capacity defined for the DCL. Data was likely truncated. -
evt_overrun(Receive, e.g., 0x05): Hardware receive FIFO overflowed. Data was likely lost. Indicates the system couldn't service the receive buffer fast enough or bus traffic exceeded capacity. -
evt_descriptor_read(e.g., 0x06): Hardware error reading the (compiled) DCL descriptor from host memory. Could indicate memory corruption or issues with the memory pointed to by the DCL structures. -
evt_data_read(Send, e.g., 0x07): Hardware error reading packet payload data from host memory during a send operation. Could indicate invalid buffer pointers or memory issues. -
evt_data_write(Receive/Timestamp/Status, e.g., 0x08): Hardware error writing received data, timestamp, or status to host memory. Could indicate invalid buffer/status/timestamp pointers or memory protection issues. -
evt_tcode_err(Send, e.g., 0x0B): Invalid transaction code detected in the generated packet header (less likely if using system header generation). -
evt_unknown(e.g., 0x0E): Unspecified hardware error occurred. -
ack_data_error(Receive, e.g., 0x1D): Data CRC error detected in the received packet, or a mismatch between the header's data length field and the actual received data.
Check the specific <IOKit/firewire/IOFireWireFamilyCommon.h> or related headers for the precise symbolic constants and values applicable to your target OS version. Always check the status after relevant DCL updates to ensure data integrity and stream health.
-
Reference Counting Errors:
- Releasing the
IOFireWireNuDCLPoolInterfacebefore releasing the associatedIOFireWireLocalIsochPortInterfaceandIOFireWireIsochChannelInterface. This deallocates the DCL program memory while the port might still need it. - Not releasing I/O Kit interfaces (
Pool,Port,Channel,Device) when done, leading to resource leaks. - Incorrectly managing
CFSetRefreference counts when usingsaveBagandSetDCLUpdateList.
- Releasing the
-
Dynamic Modification Errors:
- Modifying DCL command properties (buffers, branches) using
Set*functions on a running program without subsequently callingIOFireWireLocalIsochPortInterface::NotifywithkFWDCLModifyNotification. The changes will exist in memory but won't be reflected in the hardware execution. - Calling
Notifywith the wrong notification type (e.g., usingModifywhenUpdatewas needed, or vice-versa).
- Modifying DCL command properties (buffers, branches) using
-
Update List / Feedback Errors:
- Expecting timestamps or status codes to be valid immediately after DCL execution without enabling them (
SetDCLTimeStampPtr,SetDCLStatusPtr) and without triggering an update viaNotifywithkFWDCLUpdateNotificationon the relevant DCL(s). - Setting up update lists incorrectly (forgetting to add DCLs to the set, using the wrong trigger DCL).
- Expecting timestamps or status codes to be valid immediately after DCL execution without enabling them (
-
Buffer Management:
- Providing invalid buffer pointers or lengths during DCL allocation or modification.
- Buffer sizes not matching expected packet sizes, leading to truncation (
evt_long_packet) or inefficient use. - Not allocating extra space for received headers when
headerBytes > 0is specified inAllocateReceivePacket. - Memory lifecycle mismatch: Deallocating application buffers (e.g., via
vm_deallocateorfree) before the port has finished using them and been finalized (see Port Finalization). This often requires careful synchronization or using the finalize callback.
-
Callback Issues:
- Performing blocking operations or excessive computation directly within a
NuDCLCallback, which can stall the driver's isochronous handling thread and lead to data loss (overruns/underruns). Offload heavy work. - Incorrectly accessing shared data between the callback context and application threads without proper locking or synchronization.
- Assuming a specific execution order or timing for callbacks relative to DCL execution or updates, especially under heavy load.
- Performing blocking operations or excessive computation directly within a
-
Isochronous Resource Allocation:
- Failures in
IOFireWireIsochChannelInterface::AllocateChannel()due to insufficient bus bandwidth or the requested channel number already being in use. Requires appropriate error handling.
- Failures in
-
Pointer Types:
- Incorrectly casting between
NuDCLRefsubtypes and genericDCLCommand*orvoid*when interacting with older APIs or theNotifyfunction.
- Incorrectly casting between
This appendix presents a pseudocode representation of a practical NuDCL program designed for continuous isochronous reception. It is derived from the C++ IsochDCLManager example discussed earlier in the guide and illustrates how various NuDCL concepts (scatter/gather, linking, branching, conditional callbacks, timestamping) are combined to build a functional streaming program.
This program assumes an external BufferManager provides pre-allocated, segmented buffers for each packet, including space for headers and a timestamp. The goal is to create a circular buffer of DCL commands that continuously receive packets into these buffers and trigger a callback periodically (e.g., after each group of packets).
Assumptions/Inputs:
-
NumGroups: Total number of buffer groups. -
PacketsPerGroup: Number of packets within each group. -
CallbackInterval: Number of groups between callbacks (must be >= 1). -
BufferMgr: Provides pointers for each packet(g, p):-
IsochHdrPtr(g, p)(sizeIsochHeaderSize) -
CIPHdrPtr(g, p)(sizeCIPHeaderSize) -
DataPtr(g, p)(sizePacketDataSize) -
TimestampPtr(g, p)(pointer toUInt32)
-
-
ReceiveHeaderSize: Set to 4 bytes (requesting first quadlet of header). -
DefaultFlags:Dynamic | UpdateBeforeCallback(assumed desirable flags).
Pseudocode:
// --- Initialization ---
LET FirstDCL = NULL
LET LastDCL = NULL
LET PreviousDCL = NULL
// --- Program Creation Loop ---
FOR group = 0 TO NumGroups - 1
// Create/prepare metadata structure for this group (GroupInfo[group])
// Store 'group' index within GroupInfo[group]
FOR packet = 0 TO PacketsPerGroup - 1
// 1. Define the current DCL command (Receive Packet)
COMMAND CurrentDCL : RECEIVE_PACKET
// 2. Configure Buffers (Scatter/Gather List)
SET CurrentDCL.Buffers = [
{ address: IsochHdrPtr(group, packet), length: IsochHeaderSize },
{ address: CIPHdrPtr(group, packet), length: CIPHeaderSize },
{ address: DataPtr(group, packet), length: PacketDataSize }
]
// Inform driver we expect 4 header bytes (even though buffers are explicit)
SET CurrentDCL.ReceiveHeaderSize = 4
// 3. Configure Timestamping
SET CurrentDCL.TimestampPointer = TimestampPtr(group, packet)
// 4. Configure Flags
SET CurrentDCL.Flags = DefaultFlags // (e.g., Dynamic | UpdateBeforeCallback)
// 5. Configure Callback (Conditionally)
// Check if this is the last packet of a group triggering a callback
IF (packet == PacketsPerGroup - 1) AND ((group + 1) MOD CallbackInterval == 0) THEN
SET CurrentDCL.Callback = HandleGroupComplete // Function to call
SET CurrentDCL.Refcon = GroupInfo[group] // Pass group context to callback
ELSE
SET CurrentDCL.Callback = NULL // No callback for this DCL
SET CurrentDCL.Refcon = NULL // No specific context needed
ENDIF
// 6. Configure Linking (Sequential Build & Update Tracking)
IF PreviousDCL IS NOT NULL THEN
// Link the previous command to this one (standard flow)
SET PreviousDCL.BRANCH = CurrentDCL // Explicitly set branch to next
ENDIF
// 7. Track First and Last DCLs
IF FirstDCL IS NULL THEN
SET FirstDCL = CurrentDCL // Capture the very first command
ENDIF
SET LastDCL = CurrentDCL // Update pointer to the most recently created command
// 8. Prepare for next iteration
SET PreviousDCL = CurrentDCL
ENDFOR // End packet loop
ENDFOR // End group loop
// --- Loop Closure (Fixup) ---
// This crucial step makes the program circular.
// It's done *after* all DCLs are allocated and configured,
// and requires notifying the associated IOFireWireLocalIsochPort.
IF LastDCL IS NOT NULL AND FirstDCL IS NOT NULL THEN
SET LastDCL.BRANCH = FirstDCL // Make the last DCL jump back to the first
// In real code: Call port->Notify(kFWDCLModifyNotification, &LastDCL, 1)
ENDIF
// --- End of Program Definition ---
// --- Callback Handler Logic (Conceptual) ---
FUNCTION HandleGroupComplete(Refcon, ExecutedDCL)
// ExecutedDCL is the specific DCL command that had the callback attached
// Refcon contains the pointer to GroupInfo for the completed group
LET GroupInfo = Refcon // Cast Refcon to the relevant GroupInfo structure
LET GroupIndex = GroupInfo.groupIndex
// Optional: If timestamp/status updates were configured and notified,
// read the updated values from the relevant pointers now.
// Read TimestampPtr(GroupIndex, PacketsPerGroup - 1)
// Read StatusPtr(GroupIndex, PacketsPerGroup - 1) // Assuming status was set too
// Main Action: Notify the application layer that data for 'GroupIndex' is ready
CALL ClientDataReadyCallback(GroupIndex, GroupInfo)
// Optional: Implement buffer recycling logic here if necessary,
// which would involve modifying other DCL commands (e.g., SetDCLRanges)
// and calling port->Notify(kFWDCLModifyNotification, ...).
ENDFUNCTION
This pseudocode demonstrates the construction of a common isochronous receive pattern using NuDCL principles:
- Sequential allocation automatically links commands.
- Scatter/gather lists (SET CurrentDCL.Buffers) define complex receive targets.
- Explicit branching (SET LastDCL.BRANCH = FirstDCL) creates loops.
- Callbacks (SET CurrentDCL.Callback) provide notification points.
- Reference constants (SET CurrentDCL.Refcon) pass context to callbacks.
- Timestamping (SET CurrentDCL.TimestampPointer) enables precise timing capture (requiring subsequent updates).
- The necessity of a separate "fixup" step involving port notification for dynamic changes (like loop closure) is highlighted.