Source to iokit/Families/IONetworking/IOOutputQueue.cpp


Enter a symbol's name here to quickly find it.

/*
 * Copyright (c) 1998-2000 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * The contents of this file constitute Original Code as defined in and
 * are subject to the Apple Public Source License Version 1.1 (the
 * "License").  You may not use this file except in compliance with the
 * License.  Please obtain a copy of the License at
 * http://www.apple.com/publicsource and read it before using this file.
 * 
 * This Original Code and all software distributed under the License are
 * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT.  Please see the
 * License for the specific language governing rights and limitations
 * under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */
/*
 * Copyright (c) 1998 Apple Computer, Inc.  All rights reserved. 
 *
 * IOOutputQueue.cpp
 *
 * HISTORY
 * 2-Feb-1999       Joe Liu (jliu) created.
 *
 */

#include <IOKit/assert.h>
#include <IOKit/IOWorkLoop.h>
#include <IOKit/network/IOPacketQueue.h>
#include <IOKit/network/IOOutputQueue.h>
#include <IOKit/network/IOOQLockFIFOQueue.h>
#include <IOKit/network/IOOQGateFIFOQueue.h>
#include <IOKit/network/IONetworkStats.h>
#include <IOKit/network/IONetworkController.h>

//===========================================================================
// IOOutputQueue class.
//
// An object that queues output packets on behalf of its target, usually an 
// IONetworkController instance, and allows the target to control the packet
// flow. IOOutputQueue is is an abstract class that provides an interface for
// its subclasses. Concrete subclasses will complete the implementation while
// providing unique synchronization options.
//===========================================================================

#warning need real atomic operators
#define ATOMIC_SET(x, v)    ((x) = (v))
#define ATOMIC_ADD(x, y)    ((x) += (y))

#undef  super
#define super OSObject
OSDefineMetaClass( IOOutputQueue, OSObject )
OSDefineAbstractStructors( IOOutputQueue, OSObject )

#define kIOOQParamMagic     ((void *) 0xfeedbeef)

//---------------------------------------------------------------------------
// Initialize an IOOutputQueue instance.
//
// target: The object that shall receive packets from the queue,
//   and is usually a subclass of IONetworkController. If the target is
//   not an IONetworkController instance, then the target must immediately
//   call registerOutputHandler() after initializing the queue.
//
// capacity: The initial capacity of the output queue, defined as
//   the number of packets that the queue can hold without dropping.
//
// Returns true if initialized successfully, false otherwise.

bool IOOutputQueue::init(OSObject * target,
                         UInt32     capacity)
{
#if 0
    _target    = 0;
    _outAction = 0;
    _stats     = 0;
    _param     = 0;
    _callEntry = 0;
    _queue     = 0;
#endif

    if (!super::init())
        return false;

    if (OSDynamicCast(IONetworkController, target))
    {
        // If the target is a type of IONetworkController, then call
        // its getOutputHandler() method to obtain a pointer to its
        // output method.

        if (registerOutputHandler(target,
            ((IONetworkController *) target)->getOutputHandler()) == false)
            return false;
    }

    // Allocate an IOPacketQueue (or a subclass).
    //
    _queue = createPacketQueue(capacity);
    if (!_queue)
        return false;

    // Allocate and initialize the callout entry.
    //
    _callEntry = thread_call_allocate((thread_call_func_t)&runServiceThread,
                                      (void *)this);
    if (!_callEntry)
        return false;

    // Create a data object for queue statistics.
    //
    _statsData = IONetworkData::withName(
                 kIOOutputQueueStatsKey,
                 sizeof(IOOutputQueueStats),
                 0,
                 kIONDBasicAccessTypes | kIONDAccessTypeReset,
                 this,
                 (IONetworkData::Action)
                     &IOOutputQueue::dataReadAndResetHandler,
                 kIOOQParamMagic);
    if (!_statsData)
        return false;

    _stats = (IOOutputQueueStats *) _statsData->getBuffer();

    return true;
}

//---------------------------------------------------------------------------
// Frees the IOOutputQueue instance.

void IOOutputQueue::free()
{
    if (_statsData)
        _statsData->release();

    if (_callEntry)
    {
        cancelServiceThread();
        thread_call_free(_callEntry);
    }
    
    if (_queue)
        _queue->release();

    super::free();
}

//---------------------------------------------------------------------------
// This method is called by our IONetworkData object when it receives
// a read or a reset request. We need to be notified in order to intervene
// in the request handling.
//
// data: The IONetworkData object.
// opFlag: The operation requested.
// arg: Argument for the request.

IOReturn IOOutputQueue::dataReadAndResetHandler(IONetworkData * data,
                                                UInt32          type,
                                                void *          arg)
{
    IOReturn ret = kIOReturnSuccess;

    assert(data && (arg == kIOOQParamMagic));

    // Check the type of data request.
    //
    switch (type)
    {
        case kIONDAccessTypeRead:
        case kIONDAccessTypeSerialize:
            _stats->capacity = getCapacity();
            _stats->size     = getSize();
            _stats->peakSize = getPeakSize();
            break;

        case kIONDAccessTypeReset:
            getPeakSize(true);
            getDropCount(true);
            getOutputCount(true);
            getRetryCount(true);
            getStallCount(true);
            break;

        default:
            ret = kIOReturnNotWritable;
            break;
    }

    return ret;
}

//---------------------------------------------------------------------------
// Returns the current state of the queue object.

IOOQState IOOutputQueue::getState() const
{
    return _state;
}

//---------------------------------------------------------------------------
// Schedule a service thread callout, which will then execute the
// serviceThread() method. Subclasses should not override this method.

bool IOOutputQueue::scheduleServiceThread()
{
    thread_call_enter(_callEntry);
    return true;
}

//---------------------------------------------------------------------------
// Cancel the service thread callout. Subclasses should not override this
// method.

bool IOOutputQueue::cancelServiceThread()
{
    return thread_call_cancel(_callEntry);
}

//---------------------------------------------------------------------------
// A glue function that is registered as the service thread callout handler.
// This function in turn will call the serviceThread() method.

void IOOutputQueue::runServiceThread(IOOutputQueue * self, thread_call_param_t)
{
    assert(self);
    self->serviceThread();
}

//---------------------------------------------------------------------------
// Must be implemented by subclasses that calls scheduleServiceThread().
// The default implementation is a placeholder and performs no useful action.

void IOOutputQueue::serviceThread()
{
}

//---------------------------------------------------------------------------
// Release all packets held in the queue. The size of the queue is reset
// to zero. The drop packet counter is incremented by the number of packets
// freed. See getDropCount().
//
// Returns the number of packets freed.

UInt32 IOOutputQueue::flush()
{
    UInt32 flushCount = _queue->lockFlush();
    ATOMIC_ADD(_stats->dropCount, flushCount);
    return flushCount;
}

//---------------------------------------------------------------------------
// capacity: The new capacity of the queue.
//
// Returns true if the new capacity was accepted, false otherwise.

bool IOOutputQueue::setCapacity(UInt32 capacity)
{
    return _queue->setCapacity(capacity);
}

//---------------------------------------------------------------------------
// Returns the current queue capacity.

UInt32 IOOutputQueue::getCapacity() const
{
    return _queue->getCapacity();
}

//---------------------------------------------------------------------------
// Returns the current queue size.

UInt32 IOOutputQueue::getSize() const
{
    return _queue->getSize();
}

//---------------------------------------------------------------------------
// reset: Resets the counter if true.
//
// Return the peak queue size.

UInt32 IOOutputQueue::getPeakSize(bool reset = false)
{
    return _queue->getPeakSize(reset);
}

//---------------------------------------------------------------------------
// This method returns the number of times that a kIOOQReturnStatusDropped 
// status code is received from the target's output handler, indicating
// that the packet given was dropped. This count is also incremented when
// the queue drops a packet due to overcapacity, or by an explicit flush()
// call.
//
// reset: Resets the counter if true.
//
// Returns the number of dropped packets.
//
// FIXME - need atomic exchange

UInt32 IOOutputQueue::getDropCount(bool reset = false)
{
    UInt32 count = _stats->dropCount;
    if (reset)
        _stats->dropCount = 0;
    return count;
}

//---------------------------------------------------------------------------
// This method returns the number of times that a kIOOQReturnStatusSuccess 
// status code is received from the target's output handler, indicating
// that the packet given was accepted, and is ready to be (or already has been)
// transmitted.
//
// reset: Resets the counter if true.
//
// Returns the number of packets accepted by our target.
//
// FIXME - need atomic exchange

UInt32 IOOutputQueue::getOutputCount(bool reset = false)
{
    UInt32 count = _stats->outputCount;
    if (reset)
        _stats->outputCount = 0;
    return count;
}

//---------------------------------------------------------------------------
// This method returns the number of times that a kIOOQReturnStatusRetry
// status code is received from the target's output handler, indicating
// that the target is temporarily unable to handle the packet given, and
// the queue should try to resend the same packet at some later time.
//
// reset: Resets the counter if true.
//
// Returns the number of retries issued by the target.
//
// FIXME - need atomic exchange

UInt32 IOOutputQueue::getRetryCount(bool reset = false)
{
    UInt32 count = _stats->retryCount;
    if (reset)
        _stats->retryCount = 0;
    return count;
}

//---------------------------------------------------------------------------
// Each time the queue is stalled, when a kIOOQReturnActionStall action code
// is received from the target's output handler, a counter is incremented.
// This method returns the value stored in this counter.
//
// reset: Resets the counter if true.
//
// Returns the number of times that the queue was stalled.
//
// FIXME - need atomic exchange

UInt32 IOOutputQueue::getStallCount(bool reset = false)
{
    UInt32 count = _stats->stallCount;
    if (reset)
        _stats->stallCount = 0;
    return count;
}

//---------------------------------------------------------------------------
// Allows subclasses to override the default action, which is to allocate
// and return an IOPacketQueue instance. The returned object is used to 
// implement the queueing behavior of the IOOutputQueue.
//
// capacity: The initial capacity of the queue.
//
// Returns a newly allocated and initialized IOPacketQueue instance.

IOPacketQueue * IOOutputQueue::createPacketQueue(UInt32 capacity) const
{
    return IOPacketQueue::withCapacity(capacity);
}

//---------------------------------------------------------------------------
// Return an address of a method that is designated to handle
// packets sent to the queue object.
//
// Returns the address of the output packet handler.

IOOutputAction IOOutputQueue::getOutputHandler() const
{
    return (IOOutputAction) &IOOutputQueue::enqueue;
}

//---------------------------------------------------------------------------
// Register the target object and method to call to handle packets
// removed from the queue.
//
// handler: Pointer to an initialized IOOutputHandler structure passed in
//          by the caller.
//
// Returns true if the structure given was accepted, otherwise return false.

bool IOOutputQueue::registerOutputHandler(OSObject *      target,
                                          IOOutputAction  action)
{
    if (!target || !action)
        return false;

    // Cache the structure fields to instance variables.
    //
    _target    = target;
    _outAction = action;

    return true;
}

//---------------------------------------------------------------------------
// Return an IONetworkData object containing an IOOutputQueueStats structure.

IONetworkData * IOOutputQueue::getStatisticsData() const
{
    return _statsData;
}

//===========================================================================
// IOOQLockFIFOQueue
//===========================================================================

#undef  super
#define super IOOutputQueue
OSDefineMetaClassAndStructors( IOOQLockFIFOQueue, IOOutputQueue )

#define LOCK     IOTakeLock(_mutex)
#define UNLOCK   IOUnlock(_mutex)

//---------------------------------------------------------------------------
// Initialize an IOOQLockFIFOQueue instance.
//
// target: The object that shall receive packets from the queue,
//   and is usually a subclass of IONetworkController. If the target is
//   not an IONetworkController instance, then the target must immediately
//   call registerOutputHandler() after initializing the queue.
//
// capacity: The initial capacity of the output queue, defined as
//   the number of packets that the queue can hold without dropping.
//
// Returns true if initialized successfully, false otherwise.

bool IOOQLockFIFOQueue::init(OSObject * target, UInt32 capacity = 0)
{
    if (!super::init(target, capacity))
        return false;

    // Allocate the mutex lock.
    //
    _mutex = IOLockAlloc();
    if (!_mutex)
        return false;

    ATOMIC_SET(_state, kIOOQStateStopped);

    return true;
}

//---------------------------------------------------------------------------
// Factory method that will construct and initialize an IOOQLockFIFOQueue 
// instance.

IOOQLockFIFOQueue * IOOQLockFIFOQueue::withTarget(OSObject * target,
                                                  UInt32     capacity = 0)
{
    IOOQLockFIFOQueue * queue = new IOOQLockFIFOQueue;
    
    if (queue && !queue->init(target, capacity))
    {
        queue->release();
        queue = 0;
    }
    return queue;
}

//---------------------------------------------------------------------------
// Frees the IOOQLockFIFOQueue instance.

void IOOQLockFIFOQueue::free()
{
    cancelServiceThread();

    if (_mutex) IOLockFree(_mutex);

    super::free();
}

//---------------------------------------------------------------------------
// Provide an implementation for the interface defined in IOOutputQueue.
// This method is called by a callout thread when service() is performed 
// asynchronously.

void IOOQLockFIFOQueue::serviceThread()
{
    // Bump the enqueue count, and service the queue if 'serviceActive'
    // is false.

    ATOMIC_ADD(_enqueueCount, 1);

    if ((_state == kIOOQStateRunning) && !_serviceActive)
    {
        dequeue();
    }
}

//---------------------------------------------------------------------------
// Handles packet (or a packet chain) sent to the queue. This method can
// handle calls from multiple simultaneous client threads. A client thread
// that calls enqueue() will acquire a mutex lock and call dequeue() if it
// detects that no other thread is actively dequeueing packets from the queue.
// The dequeue() method will return when the queue becomes empty, or if the 
// target stalls the queue. This method may block its caller.
//
// m: The packet (or a packet chain) to be queued for transmission.
//
// Returns: The number of dropped packets.

UInt32 IOOQLockFIFOQueue::enqueue(struct mbuf * m)
{
    UInt32 dropped = _queue->lockEnqueue(m);

    // Increment the dropped packet counter.
    if (dropped)
        ATOMIC_ADD(_stats->dropCount, dropped);

    // Bump the enqueue count, and service the queue if 'serviceActive'
    // is false.

    ATOMIC_ADD(_enqueueCount, 1);

    if ((_state == kIOOQStateRunning) && !_serviceActive)
    {
        dequeue();
    }

    return dropped;
}

//---------------------------------------------------------------------------
// Responsible for removing all packets from the queue and calling
// the target's output handler. This method returns when the queue becomes
// empty or if the queue's state is no longer kIOOQStateRunning. The target's
// output handler is called for every packet removed from the queue. Only a
// single packet is sent to the target for every call, the packets are never
// chained. A mutex lock enforces single threaded access to the target's
// output handler.

void IOOQLockFIFOQueue::dequeue()
{
    struct mbuf *  m;
    UInt32         r;
    UInt32         dequeueCount;

    LOCK;

    do {
        // Take a snapshot of the current enqueueCount.
        //
        dequeueCount = _enqueueCount;

        _serviceActive = true;  // -- mark dequeuing active --

        while ((_state == kIOOQStateRunning) &&
                _queue->getSize() && (m = _queue->lockDequeue()))
        {
            ATOMIC_SET(_state, kIOOQStateStalled);

            // Call the target's output handler.
            //
            r = (_target->*_outAction)(m);

            // Decide the fate of the dequeued packet and
            // update statistics counters.
            //
            switch (r & kIOOQReturnStatusMask)
            {
                default:
                case kIOOQReturnStatusDropped:
                    ATOMIC_ADD(_stats->dropCount, 1);
                    break;

                case kIOOQReturnStatusSuccess:
                    ATOMIC_ADD(_stats->outputCount, 1);
                    break;

                case kIOOQReturnStatusRetry:
                    _queue->lockPrepend(m);
                    ATOMIC_ADD(_stats->retryCount, 1);
                    break;
            }

            // Handle the requested action.
            //
            switch (r & kIOOQReturnActionMask)
            {
                default:
                case kIOOQReturnActionNone:
                    ATOMIC_SET(_state, kIOOQStateRunning);
                    break;

                case kIOOQReturnActionStall:
                    ATOMIC_ADD(_stats->stallCount, 1);
                    break;
            }

            // Consume all enqueueCounts before looping.
            //
            dequeueCount = _enqueueCount;

        } /* while [ running and queue has packets ] */

        _serviceActive = false; // -- mark dequeuing inactive --

    } while (dequeueCount != _enqueueCount);

    UNLOCK;
}

//---------------------------------------------------------------------------
// This method is called by the target to start the queue. Once started,
// the queue will be allowed to call the target's output handler.
// Before that, with the queue stopped, the queue will absorb incoming
// packets sent to the enqueue() method, but no packets will be dequeued,
// and the target's output handler will not be called.
//
// Returns true if the queue was successfully started, false otherwise.

bool IOOQLockFIFOQueue::start()
{
    service(true);
    return true;    // always return success
}

//---------------------------------------------------------------------------
// Stops the queue to prevent it from calling the target's output handler.
// This call is synchronous the caller may block. The target's output handler 
// must never call this method, or any other queue methods.

void IOOQLockFIFOQueue::stop()
{
    LOCK;
    ATOMIC_SET(_state, kIOOQStateStopped);
    UNLOCK;
}

//---------------------------------------------------------------------------
// If the queue becomes stalled, then service() must be called to restart
// the queue when the target is ready to accept more packets. If the queue
// is not empty, this method will also call dequeue(). Note that if the
// target never sends a kIOOQReturnActionStall action code to the queue,
// then the queue will never stall on its own accord. Calling this method
// on a running queue that is not stalled is harmless.
//
// sync: True if the service action should be performed synchronously,
//   false to perform the action asynchronously without blocking the caller,
//   but with a much higher latency cost.
//
// Returns true if the queue needed servicing, false otherwise.

bool IOOQLockFIFOQueue::service(bool sync = true)
{
    bool started = false;

    ATOMIC_SET(_state, kIOOQStateRunning);

    if (_queue->getSize())
    {
        ATOMIC_ADD(_enqueueCount, 1);

        if (!_serviceActive)
        {
            if (sync)
                dequeue();
            else
                scheduleServiceThread();

            started = true;
        }
    }

    return started;
}

//===========================================================================
// IOOQGateFIFOQueue
//===========================================================================

#undef  super
#define super IOOutputQueue
OSDefineMetaClassAndStructors( IOOQGateFIFOQueue, IOOutputQueue )

#define GATED_DEQUEUE   _gate->runCommand()

//---------------------------------------------------------------------------
// Initialize an IOOQGateFIFOQueue instance.
//
// target: The object that shall receive packets from the queue,
//   and is usually a subclass of IONetworkController. If the target is
//   not an IONetworkController instance, then the target must immediately
//   call registerOutputHandler() after initializing the queue.
//
// workloop: A workloop object provided by the target that the
//   queue will use to add an internal IOCommandGate as an event source.
//
// capacity: The initial capacity of the output queue, defined as
//   the number of packets that the queue can hold without dropping.
//
// Returns true if initialized successfully, false otherwise.

bool IOOQGateFIFOQueue::init(OSObject *        target,
                             IOWorkLoop *      workloop,
                             UInt32            capacity = 0)
{
    if (!super::init(target, capacity))
        return false;

    // Verify that the IOWorkLoop is valid.
    //
    if (!OSDynamicCast(IOWorkLoop, workloop))
        return false;

    // Allocate and attach an IOCommandGate instance to the workloop.
    // Set the default action to gatedDequeue().
    //
    _gate = IOCommandGate::commandGate(this,
               (IOCommandGate::Action) &IOOQGateFIFOQueue:: gatedDequeue);
    if (!_gate || (workloop->addEventSource(_gate) != kIOReturnSuccess))
        return false;

    ATOMIC_SET(_state, kIOOQStateStopped);

    return true;
}

//---------------------------------------------------------------------------
// Factory method that will construct and initialize an IOOQGateFIFOQueue 
// instance.
//
// Returns an IOOQGateFIFOQueue instance upon success, or 0 otherwise.

IOOQGateFIFOQueue *
IOOQGateFIFOQueue::withTarget(OSObject *   target,
                              IOWorkLoop * workloop,
                              UInt32       capacity = 0)
{
    IOOQGateFIFOQueue * queue = new IOOQGateFIFOQueue;
    
    if (queue && !queue->init(target, workloop, capacity))
    {
        queue->release();
        queue = 0;
    }
    return queue;
}

//---------------------------------------------------------------------------
// Frees the IOOQGateFIFOQueue instance.

void IOOQGateFIFOQueue::free()
{
    cancelServiceThread();

    if (_gate)
        _gate->release();

    super::free();
}

//---------------------------------------------------------------------------
// Provide an implementation for the interface defined in IOOutputQueue.
// This method is called by a callout thread when service() is performed 
// asynchronously.

void IOOQGateFIFOQueue::serviceThread()
{
    // Bump the enqueue count, and service the queue if 'serviceActive'
    // is false.

    ATOMIC_ADD(_enqueueCount, 1);

    if ((_state == kIOOQStateRunning) && !_serviceActive)
    {
        GATED_DEQUEUE;
    }
}

//---------------------------------------------------------------------------
// Handles packet (or a packet chain) sent to the queue. This method can
// handle calls from multiple simultaneous client threads. A client thread
// that calls enqueue() will acquire a mutex lock and call dequeue() if it
// detects that no other thread is actively dequeueing packets from the queue.
// The dequeue() method will return when the queue becomes empty, or if the 
// target stalls the queue. This method may block its caller.
//
// m: The packet (or a packet chain) to be queued for transmission.

UInt32 IOOQGateFIFOQueue::enqueue(struct mbuf * m)
{
    UInt32 dropped = _queue->lockEnqueue(m);

    // Increment the dropped packet counter.
    if (dropped)
        ATOMIC_ADD(_stats->dropCount, dropped);

    // Bump the enqueue count, and service the queue if 'serviceActive'
    // is false.

    ATOMIC_ADD(_enqueueCount, 1);

    if ((_state == kIOOQStateRunning) && !_serviceActive)
    {
        GATED_DEQUEUE;
    }

    return dropped;
}

//---------------------------------------------------------------------------
// Responsible for removing all packets from the queue and calling
// the target's output handler. This method returns when the queue becomes
// empty or if the queue's state is no longer kIOOQStateRunning. The target's
// output handler is called for every packet removed from the queue. Only a
// single packet is sent to the target for every call, the packets are never
// chained. An IOCommandGate attached to an workloop provided by the target
// ensures mutual exclusion between the dequeueing action (and calls to the
// target's output handler), and any other action performed by the workloop's
// thread.

void IOOQGateFIFOQueue::dequeue()
{
    GATED_DEQUEUE;
}

void IOOQGateFIFOQueue::gatedDequeue()
{
    struct mbuf *  m;
    UInt32         r;
    UInt32         dequeueCount;

    do {
        // Take a snapshot of the current enqueueCount.
        //
        dequeueCount = _enqueueCount;

        _serviceActive = true;  // -- mark dequeuing active --

        while ((_state == kIOOQStateRunning) &&
                _queue->getSize() && (m = _queue->lockDequeue()))
        {
            ATOMIC_SET(_state, kIOOQStateStalled);

            // Call the controller's output handler.
            //
            r = (_target->*_outAction)(m);

            // Decide what should happen to the dequeued packet and
            // update statistics counters.
            //
            switch (r & kIOOQReturnStatusMask)
            {
                default:
                case kIOOQReturnStatusDropped:
                    ATOMIC_ADD(_stats->dropCount, 1);
                    break;

                case kIOOQReturnStatusSuccess:
                    ATOMIC_ADD(_stats->outputCount, 1);
                    break;

                case kIOOQReturnStatusRetry:
                    _queue->lockPrepend(m);
                    ATOMIC_ADD(_stats->retryCount, 1);
                    break;
            }

            // Handle the requested action.
            //
            switch (r & kIOOQReturnActionMask)
            {
                default:
                case kIOOQReturnActionNone:
                    ATOMIC_SET(_state, kIOOQStateRunning);
                    break;

                case kIOOQReturnActionStall:
                    ATOMIC_ADD(_stats->stallCount, 1);
                    break;
            }

            // Consume all enqueueCounts before looping.
            //
            dequeueCount = _enqueueCount;

        } /* while [ running and queue has packets ] */

        _serviceActive = false; // -- mark dequeuing inactive --

    } while (dequeueCount != _enqueueCount);
}

//---------------------------------------------------------------------------
// This method is called by the target to start the queue. Once started,
// the queue will be allowed to call the target's output handler.
// Before that, with the queue stopped, the queue will absorb incoming
// packets sent to the enqueue() method, but no packets will be dequeued,
// and the target's output handler will not be called.
//
// Returns true if the queue was successfully started, false otherwise.

bool IOOQGateFIFOQueue::start()
{
    service(true);
    return true;    // always return success
}

//---------------------------------------------------------------------------
// Stops the queue to prevent it from calling the target's output handler.
// This call is synchronous the caller may block. The target's output handler 
// must never call this method, or any other queue methods.

void IOOQGateFIFOQueue::stop()
{   
    _gate->runAction( (IOCommandGate::Action) &IOOQGateFIFOQueue::gatedStop );
}

void IOOQGateFIFOQueue::gatedStop()
{
    ATOMIC_SET(_state, kIOOQStateStopped);
}

//---------------------------------------------------------------------------
// If the queue becomes stalled, then service() must be called to restart
// the queue when the target is ready to accept more packets. If the queue
// is not empty, this method will also call dequeue(). Note that if the
// target never sends a kIOOQReturnActionStall action code to the queue,
// then the queue will never stall on its own accord. Calling this method
// on a running queue that is not stalled is harmless.
//
// sync: True if the service action should be performed synchronously,
//   false to perform the action asynchronously without blocking the caller,
//   but with a much higher latency cost.
//
// Returns true if the queue needed servicing, false otherwise.

bool IOOQGateFIFOQueue::service(bool sync = true)
{
    bool started = false;

    ATOMIC_SET(_state, kIOOQStateRunning);

    if (_queue->getSize())
    {
        ATOMIC_ADD(_enqueueCount, 1);

        if (!_serviceActive)
        {
            if (sync)
                GATED_DEQUEUE;
            else
                scheduleServiceThread();

            started = true;
        }
    }

    return started;
}