Skip to content

Apple API Integration NuDCL Guide

Aleksandr Shabelnikov edited this page Apr 12, 2025 · 3 revisions

Table of Contents: Guide to NuDCL for Modern macOS FireWire Development

Introduction to NuDCL

Purpose: Managing Isochronous Data Streams

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.

Context: I/O Kit FireWire Family on macOS

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 IOFireWireDevice user client objects.
  • Specific functionalities, like isochronous streaming, are accessed through specialized interfaces obtained from the IOFireWireDevice.
  • The IOFireWireNuDCLPoolInterface is obtained from an IOFireWireDevice and 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.

NuDCL vs. Older DCL Approaches (Conceptual)

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 NuDCLRef subtypes (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 CFSetRef and CFArrayRef for 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.

Fundamental Concepts

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.

Isochronous Transfers on FireWire

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.

Data Stream Control Language (DCL) Principles

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.

Programs as Linked Lists of Commands

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 next pointer 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 NULL next pointer (and no overriding branch) is reached.

NuDCL provides the API (IOFireWireNuDCLPoolInterface) to build these linked lists programmatically.

Hardware-Agnostic DMA Programming

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.

Scatter/Gather I/O

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.

Role of Isochronous Ports and Channels

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:

  1. Creating a NuDCL program using the pool.
  2. Creating a local isoch port and associating the NuDCL program with it.
  3. Creating an isochronous channel object.
  4. Adding the local port (as talker or listener) to the channel.
  5. (Optionally) Adding representations of remote ports if coordinating with other devices.
  6. Allocating resources and starting/stopping the stream via the channel interface.

The NuDCL Pool (IOFireWireNuDCLPoolInterface)

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.

Overview: The NuDCL Factory

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 PrintProgram to 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.

Obtaining from IOFireWireDevice

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:

  1. Get an io_service_t: Find the I/O Kit service object for the desired FireWire device (e.g., using matching dictionaries and IOServiceGetMatchingServices).
  2. Create an IOCFPlugInInterface: Use IOCreatePlugInInterfaceForService to create a generic plug-in interface for the service.
  3. Query for the IOFireWireDevice Interface: Use the plug-in interface's QueryInterface method, requesting the appropriate IOFireWireDevice interface UUID (e.g., kIOFireWireDeviceInterfaceID_v3). This yields an IOFireWireLibDeviceRef.
  4. Query for the NuDCL Pool Interface: Use the IOFireWireLibDeviceRef's QueryInterface method, requesting the kIOFireWireNuDCLPoolInterfaceID UUID.
// 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.

Lifecycle and Resource Management

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.

Building NuDCL Programs

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.

Typical Workflow

Building a NuDCL program generally follows these steps:

  1. Obtain the Pool: Get a valid IOFireWireLibNuDCLPoolRef from the target IOFireWireLibDeviceRef.
  2. (Optional) Initialize Tracking: Create a CFMutableSetRef if you plan to track specific DCLs for later updates (using the saveBag parameter during allocation).
  3. (Optional) Set Defaults: If creating a send program, you might call SetCurrentTagAndSync on the pool to define default tag/sync values for subsequent send DCL allocations.
  4. Allocate Commands: Call the appropriate allocation functions on the NuDCL pool interface (AllocateSendPacket, AllocateReceivePacket, AllocateSkipCycle, etc.) in the desired execution order. Pass the saveBag set if tracking is needed. Each successful allocation appends the new command to the end of the program list being built within the pool.
  5. 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).
  6. Retrieve Program: Once the program structure is complete, use GetProgram() or GetDCLs() to get a reference to the constructed program, typically needed when associating it with an IOFireWireLocalIsochPortInterface.
  7. Associate with Port: Create an IOFireWireLocalIsochPortInterface and associate the NuDCL program with it (details vary depending on the specific port creation method, which often implicitly takes the program start).

Sequential Allocation & Implicit Linking

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 next pointer (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 next pointer initially set to NULL.

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).

Tracking DCLs with CFMutableSetRef (saveBag)

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:

  1. Create a mutable set: CFMutableSetRef mySet = CFSetCreateMutable(kCFAllocatorDefault, 0, NULL); (Using NULL for callbacks means the set will simply store the NuDCLRef pointers).
  2. Pass mySet as the saveBag argument during allocation: dcl = pool->AllocateReceivePacket(pool, mySet, ...);
  3. The dcl reference will be added tomySet.
  4. Later, you can use mySet (e.g., pass it to SetDCLUpdateList). Remember that SetDCLUpdateList consumes a reference, so you might need to CFRetain the set or create copies if using it multiple times.
  5. 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.

Retrieving the Program (GetProgram, GetDCLs)

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 NuDCL command 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::CreateLocalIsochPort which 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).
  • CFArrayRef GetDCLs( IOFireWireLibNuDCLPoolRef self ):
    • This function returns a CFArrayRef containing all the NuDCLRef objects 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 CFArrayRef using CFRelease.

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 Command Types

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.

Send Commands (NuDCLSendPacketRef)

These commands are responsible for transmitting isochronous data packets onto the FireWire bus.

Allocation (AllocateSendPacket / AllocateSendPacket_v)

  • 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 NuDCLSendPacketRef on success, or NULL on failure (e.g., pool is out of memory).

Isochronous Header Generation

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.

Tag and Sync Bits

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 SetDCLTagBits and SetDCLSyncBits.

Receive Commands (NuDCLReceivePacketRef)

These commands define how incoming isochronous data packets from the bus are received and placed into system memory.

Allocation (AllocateReceivePacket / AllocateReceivePacket_v)

  • 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 NuDCLReceivePacketRef on success, or NULL on failure.

Handling Received Headers

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.

Control Flow: Branching

NuDCL achieves control flow not through dedicated jump/label command types, but by setting a branch target on any existing NuDCL command.

Setting Branches (SetDCLBranch)

  • 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 branchDCL next.
  • Applies To: Any NuDCLRef (Send, Receive, SkipCycle).

Implementing Loops

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 recv1

Program Termination (Branch to NULL)

Setting 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.

Timing & Synchronization

NuDCL provides mechanisms to control timing and obtain synchronization information.

Skipping Cycles (AllocateSkipCycle, NuDCLSkipCycleRef)

  • 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.

Timestamping (SetDCLTimeStampPtr)

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.

Callbacks, Status, and Metadata

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.

Configuring NuDCL Commands (API Details)

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.

Common Configuration API

These functions can be used to configure properties applicable to any type of NuDCL command (NuDCLRef, including send, receive, and skip types).

Linking & Branching (Set/GetDCLBranch, FindDCLNextDCL)

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. If NULL, the program path terminates after dcl.
    • Notes: Essential for creating loops and non-linear program flow.
  • NuDCLRef GetDCLBranch(NuDCLRef dcl)

    • Purpose: Retrieves the currently set branch target for dcl.
    • dcl: The command to query.
    • Return: The NuDCLRef of the branch target, or NULL if no branch is set (or if it branches to termination).
  • NuDCLRef FindDCLNextDCL(IOFireWireLibNuDCLPoolRef self, NuDCLRef dcl)

    • Purpose: Finds the command that was allocated immediately after dcl in the pool's sequence.
    • self: The NuDCL pool interface.
    • dcl: The command whose sequential successor is desired.
    • Return: The NuDCLRef of the next allocated command, or NULL if dcl was the last one allocated.
    • Notes: Useful for understanding the allocation order, independent of any explicit branching set via SetDCLBranch.

Callbacks & Refcons (Set/GetDCLCallback, Set/GetDCLRefcon)

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 when dcl executes.
    • dcl: The command to associate the callback with.
    • callback: The function to call. The function signature is typically void (*NuDCLCallback)(NuDCLRef dcl, void* refcon).
    • Notes: If the DCL also has an update list, the callback might occur after the update completes.
  • NuDCLCallback GetDCLCallback(NuDCLRef dcl)

    • Purpose: Retrieves the callback function associated with dcl.
    • dcl: The command to query.
    • Return: The function pointer, or NULL if none is set.
  • void SetDCLRefcon(NuDCLRef dcl, void* refcon)

    • Purpose: Associates an arbitrary user-defined pointer (void*) with dcl.
    • dcl: The command to associate the data with.
    • refcon: The user data pointer.
    • Notes: This refcon is typically passed as the second argument to the NuDCLCallback function, providing context (e.g., identifying the buffer related to the callback).
  • void* GetDCLRefcon(NuDCLRef dcl)

    • Purpose: Retrieves the user reference constant associated with dcl.
    • dcl: The command to query.
    • Return: The user data pointer, or NULL if none was set.

Timestamping (Set/GetDCLTimeStampPtr)

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 a UInt32 variable in application memory.
    • Notes: The value at timeStampPtr is not valid until an update notification (Notify) has been processed for dcl after it has executed.
  • UInt32* GetDCLTimeStampPtr(NuDCLRef dcl)

    • Purpose: Retrieves the pointer previously set by SetDCLTimeStampPtr.
    • dcl: The command to query.
    • Return: The pointer, or NULL if timestamping is not enabled for dcl.

Status Reporting (Set/GetDCLStatusPtr)

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 a UInt32 variable in application memory.
    • Notes: Status codes indicate success (ack_complete) or various errors (overrun, timeout, CRC error, etc. - see header for codes). The value at statusPtr is not valid until an update notification (Notify) has been processed for dcl after it has executed.
  • UInt32* GetDCLStatusPtr(NuDCLRef dcl)

    • Purpose: Retrieves the pointer previously set by SetDCLStatusPtr.
    • dcl: The command to query.
    • Return: The pointer, or NULL if status reporting is not enabled for dcl.

Update Lists (Set/Append/Copy/RemoveDCLUpdateList)

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 the CFSetRef) that should be updated after dcl executes.
    • dcl: The command whose execution triggers the updates.
    • dclList: A CFSetRef containing the NuDCLRefs to be updated. The pool interface consumes one reference count of this set.
    • Notes: Often used with sets created using the saveBag parameter during allocation. Essential for receive operations and status/timestamp checking.
  • IOReturn AppendDCLUpdateList(NuDCLRef dcl, NuDCLRef updateDCL)

    • Purpose: Adds a single NuDCLRef (updateDCL) to the existing update list of dcl.
    • dcl: The command whose execution triggers the update.
    • updateDCL: The command to add to the update list.
  • CFSetRef CopyDCLUpdateList(NuDCLRef dcl)

    • Purpose: Retrieves a copy of the update list associated with dcl.
    • dcl: The command to query.
    • Return: A new CFSetRef containing the NuDCLRefs in the update list. The caller owns this returned set and must CFRelease it. Returns NULL if no list is set.
  • IOReturn RemoveDCLUpdateList(NuDCLRef dcl)

    • Purpose: Clears the update list associated with dcl.
    • dcl: The command whose update list is to be removed.

Flags & Wait Control (Set/GetDCLFlags, SetDCLWaitControl)

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.

Transfer Command API (Send/Receive)

These functions configure properties specific to commands that transfer data payloads (NuDCLSendPacketRef and NuDCLReceivePacketRef).

Buffer Management (Set/Append/GetDCLRanges, CountDCLRanges, GetDCLSpan, GetDCLSize)

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 dcl for data transfer. Overwrites any previously set ranges.
    • dcl: The send or receive command to configure.
    • numRanges / ranges or firstRange, ...: The scatter/gather list.
    • Notes: Critical for defining where data comes from (send) or goes to (receive).
  • 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.
  • 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 the outRanges buffer can hold.
    • outRanges: A caller-provided buffer to be filled with the IOVirtualRange structures.
    • Return: The actual number of ranges copied into outRanges.
  • UInt32 CountDCLRanges(NuDCLRef dcl)

    • Purpose: Returns the number of separate buffer ranges currently defined for dcl.
    • dcl: The command to query.
  • 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.
  • 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-provided IOVirtualRange structure to be filled with the result.
    • Notes: Useful for understanding the total memory footprint touched by the DCL, even if it's non-contiguous.

Send Command Specific API

These functions apply only to NuDCLSendPacketRef commands.

Tag/Sync (SetCurrentTagAndSync, Set/GetDCLTagBits, Set/GetDCLSyncBits)

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).
  • 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.
  • 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.

User Headers (Set/GetDCLUserHeaderPtr, GetUserHeaderMaskPtr)

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 from headerPtr where the mask has a '1', and generate the bits where the mask has 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.
  • 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.

Executing and Modifying NuDCL Programs

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.

Association with IOFireWireLocalIsochPort

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 IOFireWireLocalIsochPortInterface holds 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.

Starting & Stopping via IOFireWireIsochChannel

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.

The Need for Dynamic Updates

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 SkipCycle commands 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.

Notifying the Port (IOFireWireLocalIsochPortInterface::Notify)

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 by SetDCLTimeStampPtr, 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 expects void **. While you will likely have NuDCLRef types, you will need to cast them appropriately when creating this list (e.g., create an array NuDCLRef modifiedDCLs[] = {dcl1, dcl2}; and pass (void**)modifiedDCLs). Be cautious with pointer types and sizes.
    • numDCLs: The number of DCL pointers in inDCLList.

Workflow for Dynamic Modification:

  1. Use IOFireWireNuDCLPoolInterface functions (e.g., SetDCLRanges, SetDCLBranch) to modify the desired NuDCLRef(s) in memory.
  2. Create an array containing pointers to the modified NuDCLRef(s).
  3. Call the Notify method on the associated IOFireWireLocalIsochPortInterface, passing kFWDCLModifyNotification and the array of modified DCLs.

Workflow for Getting Updates (Status/Timestamp):

  1. Ensure SetDCLTimeStampPtr and/or SetDCLStatusPtr were called for the relevant DCLs during setup.
  2. Often, within a callback triggered by a preceding DCL's execution, or based on other timing, identify the DCLs whose status you need.
  3. Create an array containing pointers to these NuDCLRef(s).
  4. Call the Notify method on the associated IOFireWireLocalIsochPortInterface, passing kFWDCLUpdateNotification and the array of DCLs to be updated.
  5. After the Notify call 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.

Callback Handling Strategies

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 SetDCLRanges to point the same DCL command to a new empty buffer and then call Notify with kFWDCLModifyNotification.
    • 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 via SetDCLRanges and Notify.
  • 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.

Resource Cleanup and Port Finalization

Properly cleaning up resources is vital to avoid leaks and deadlocks.

  • Stopping the Stream: Always Stop() the IOFireWireIsochChannelInterface before 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 IOFireWireLocalIsochPortInterface provides SetFinalizeCallback. This callback is invoked after the port has been stopped and all pending operations (including DCL execution, updates, and user-set callbacks like NuDCLCallback) associated with that port have completed.
  • Safe Cleanup Sequence:
    1. Call Stop() on the IOFireWireIsochChannelInterface.
    2. (Optional but recommended) Set a finalize callback on the IOFireWireLocalIsochPortInterface using SetFinalizeCallback.
    3. Wait for the finalize callback to be invoked. This confirms the port is truly idle.
    4. Call ReleaseChannel() on the IOFireWireIsochChannelInterface.
    5. Call Release() on the IOFireWireLocalIsochPortInterface.
    6. Call Release() on the IOFireWireNuDCLPoolInterface (this frees the DCL program memory).
    7. Deallocate application buffers associated with the stream.

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.

Practical Usage Scenarios

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.

Scenario 1: Basic Isochronous Send Loop

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.

Scenario 2: Isochronous Receive Loop with Buffer Updates

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.

Scenario 4: Utilizing Update Lists for Feedback

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.

Key I/O Kit Interfaces

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.

IOFireWireDevice

  • 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., IOServiceGetMatchingServices with appropriate matching dictionaries).
  • Interaction with NuDCL: The IOFireWireDevice user client interface (specifically, an IOFireWireLibDeviceRef obtained via QueryInterface) is the factory for the IOFireWireNuDCLPoolInterface. 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.

IOFireWireLocalIsochPortInterface

  • 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 starting DCLCommand* of the NuDCL program (obtained via IOFireWireNuDCLPoolInterface::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 call Notify() 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 the SetFinalizeCallback() method to ensure all operations are complete before final cleanup.

IOFireWireIsochChannelInterface

  • 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 using SetTalker() or AddListener().
    • 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(), and ReleaseChannel() methods which synchronize the setup, execution, and teardown across all ports associated with that channel. Calling Start() on the channel effectively starts the execution of the NuDCL program(s) associated with its port(s).

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.

Debugging and Troubleshooting

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.

Using PrintProgram and PrintDCL

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.
  • 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 Notify call before their in-memory representation (and thus the PrintDCL output) reflects the hardware state.
    • Output: Provides a detailed breakdown of the specified NuDCLRef's properties.

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.

Understanding Status Codes

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.

Common Pitfalls

  • Reference Counting Errors:

    • Releasing the IOFireWireNuDCLPoolInterface before releasing the associated IOFireWireLocalIsochPortInterface and IOFireWireIsochChannelInterface. 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 CFSetRef reference counts when using saveBag and SetDCLUpdateList.
  • Dynamic Modification Errors:

    • Modifying DCL command properties (buffers, branches) using Set* functions on a running program without subsequently calling IOFireWireLocalIsochPortInterface::Notify with kFWDCLModifyNotification. The changes will exist in memory but won't be reflected in the hardware execution.
    • Calling Notify with the wrong notification type (e.g., using Modify when Update was needed, or vice-versa).
  • 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 via Notify with kFWDCLUpdateNotification on the relevant DCL(s).
    • Setting up update lists incorrectly (forgetting to add DCLs to the set, using the wrong trigger DCL).
  • 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 > 0 is specified in AllocateReceivePacket.
    • Memory lifecycle mismatch: Deallocating application buffers (e.g., via vm_deallocate or free) 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.
  • 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.
  • Pointer Types:

    • Incorrectly casting between NuDCLRef subtypes and generic DCLCommand* or void* when interacting with older APIs or the Notify function.

Appendix A: Case Study - Continuous Isochronous Receive Program

A.1 Introduction

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).

A.2 Pseudocode DCL Program Structure

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) (size IsochHeaderSize)
    • CIPHdrPtr(g, p) (size CIPHeaderSize)
    • DataPtr(g, p) (size PacketDataSize)
    • TimestampPtr(g, p) (pointer to UInt32)
  • 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

A.3 Summary

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.

Clone this wiki locally