Source to iokit/Families/IOUSBBus/IOUSBHub.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.
 *
 * HISTORY
 *
 */

#include <libkern/OSByteOrder.h>

#include "IOUSBHub.h"
#include <IOKit/usb/IOUSBInterface.h>
#include <IOKit/usb/IOUSBPipe.h>

#define super IOService
#define self this
#define DEBUGGING_LEVEL 0
#define DEBUGLOG kprintf

UInt32 GetErrataBits(IOUSBDeviceDescriptor *desc);

/* private API */
static void callStartHandler(void *hub)
{
    ((IOUSBHub *) hub)->StartHandler();
}

OSDefineMetaClassAndStructors(IOUSBHub, IOService)

bool IOUSBHub::init( OSDictionary * propTable )
{
    if( !super::init(propTable))
        return (false);

    _numCaptive = 0;

    return(true);
}

bool IOUSBHub::start(IOService * provider)
{
    IOReturn			err = 0;
    IOUSBDevice::FindInterfaceRequest	req;
    IOThread 			ioThread;
    
    if( !super::start(provider))
        return (false);

    // remember my device
    _device		= (IOUSBDevice *) provider;
    _address		= _device->address();
    _bus		= _device->bus();

    if (!_device->open(this)) return false;
    
    do {
        IOLog("%s: USB Generic Hub @ %d\n", getName(), _address);

        // Find the first config/interface
        if (_device->deviceDescriptor()->numConf < 1)
        {
            IOLog("%s: no hub configurations\n", getName());
            break;
        }

        req.theClass = kUSBHubClass;
	req.subClass = req.protocol = 0;
        req.maxPower = 0;
        if ((_hubInterface = _device->findNextInterface(NULL, &req)) == 0)
        {
            IOLog("%s: no interface found\n", getName());
            break;
        }
        _busPowered = req.busPowered == 2;	//FIXME
        _selfPowered = req.selfPowered == 2;	//FIXME

        if( !(_busPowered || _selfPowered) )
        {
            IOLog("%s: illegal device config - no power\n", getName());
            break;
        }

        // set my configuration

        if ((err = _device->SetConfiguration(_hubInterface->getConfigValue())))
            break;

        // Get the hub descriptor
        if ((err = GetHubDescriptor(&_hubDescriptor)))
            break;

	// Setup for reading status pipe
        _buffer = IOBufferMemoryDescriptor::withCapacity(8, kIODirectionIn);

        if (!_hubInterface->open(this))
            break;

        IOUSBFindEndpointRequest request;
        request.type = kUSBInterrupt;
        request.direction = kUSBIn;
        _interruptPipe = _hubInterface->findNextPipe(NULL, &request);

        if(!_interruptPipe)
            return false;

        // prepare the ports
	UnpackPortFlags();
        CountCaptivePorts();
#if (DEBUGGING_LEVEL > 0)
        PrintHubDescriptor(&_hubDescriptor);
#endif
        if (!CheckPortPowerRequirements())	continue;
        if (!AllocatePortMemory())		continue;
        if (!StartPorts())			continue;
        ioThread = IOCreateThread(callStartHandler, this);
        if (ioThread == NULL) 			continue;
                
        return(true);

    } while (false);

    IOLog("%s: aborting startup.  err = %d\n", getName(), err);

    _device->close(this);
    stop(provider);
    
    return(false);
}

void IOUSBHub::stop(IOService * provider)
{
    // stop/close all ports
    // deallocate ports
    StopPorts();

    if (_buffer) {
        _buffer->release();
	_buffer = 0;
    }
    if(_hubInterface) {
        _hubInterface->close(this);
        _hubInterface->release();
        _hubInterface = 0;
    }
    super::stop(provider);
}

/**********************************************************************
 **
 ** HUB FUNCTIONS
 **
 **********************************************************************/
void IOUSBHub::UnpackPortFlags(void)
{
    // ERIC FIXME?- I don't think the first +1 should be there.
    int numFlags = ((_hubDescriptor.numPorts + 1) / 8) + 1;
    int i;

    for(i = 0; i < numFlags; i++)
    {
        _hubDescriptor.pwrCtlPortFlags[i] =
        			_hubDescriptor.removablePortFlags[numFlags+i];
        _hubDescriptor.removablePortFlags[numFlags+i] = 0;
    }
}

void IOUSBHub::CountCaptivePorts(void)
{
    int 		portMask = 2;
    int 		portByte = 0;
    int			currentPort;


    for (currentPort = 1; currentPort <= _hubDescriptor.numPorts; currentPort++)
    {
        /* determine if the port is a captive port */
        if ((_hubDescriptor.removablePortFlags[portByte] & portMask) != 0)
            _numCaptive++;		// save this for power calculations

        portMask <<= 1;
        if(portMask > 0x80)
        {
            portMask = 1;
            portByte++;
        }
    }
}


/*
 ExtPower   Good      off

Bus  Self

0     0     Illegal config
1     0     Always 100mA per port
0     1     500mA     0 (dead)
1     1     500      100
*/
bool IOUSBHub::CheckPortPowerRequirements(void)
{
    IOReturn	err = kIOReturnSuccess;
    /* Note hub current in units of 1mA, everything else in units of 2mA */
    UInt32	hubPower = _hubDescriptor.hubCurrent/2;
    UInt32	busPower = _device->busPowerAvailable();
    UInt32	powerAvailForPorts, powerNeededForPorts;
    bool	startExternal;

    do
    {
        if (hubPower > busPower)
        {
            IOLog("%s: hub needs more power than available", getName());
            err = kIOReturnNoPower;
        }

        powerAvailForPorts = busPower - hubPower;
        /* we minimally need make available 100mA per non-captive port */
        powerNeededForPorts =
            (_hubDescriptor.numPorts - _numCaptive) * kUSB100mA;
        _busPowerGood = powerAvailForPorts >= powerNeededForPorts;
        if(_numCaptive > 0)
        {
            if(_busPowerGood)
                _powerForCaptive =
                    (powerAvailForPorts - powerNeededForPorts) / _numCaptive;
            else
                _powerForCaptive = powerAvailForPorts / _numCaptive;
        }

        if( (_errataBits & kErrataCaptiveOK) != 0)
            _powerForCaptive = kUSB100mAAvailable;

#if (DEBUGGING_LEVEL > 0)
        DEBUGLOG("%s: power:\n", getName());
        DEBUGLOG("\tbus power available = %ldmA\n", busPower * 2);
        DEBUGLOG("\thub power needed = %ldmA\n", hubPower * 2);
        DEBUGLOG("\tport power available = %ldmA\n", powerAvailForPorts * 2);
        DEBUGLOG("\tport power needed = %ldmA\n", powerNeededForPorts * 2);
        DEBUGLOG("\tpower for captives = %ldmA\n", _powerForCaptive * 2);
        DEBUGLOG("\tbus power is %s\n", _busPowerGood?"good" : "insufficient");
#endif
        
        _selfPowerGood = false;
        
        if(_selfPowered)
        {
            USBStatus	status = 0;

            if ((err = _device->GetDeviceStatus(&status))) 	break;
            status = USBToHostWord(status);
            _selfPowerGood = ((status & 1) != 0);	// FIXME 1?
        }

        if(_selfPowered && _busPowered)
        {
            /* Dual power hub */
            DEBUGLOG("%s: Hub attached - Self/Bus powered, ", getName());
            if(_selfPowerGood)
                DEBUGLOG("power supply good\n");
            else
                DEBUGLOG("no external power\n");
        }
        else
        {
            /* Single power hub */
            if(_selfPowered)
            {
                DEBUGLOG("%s: Hub attached - Self powered, ", getName());
                if(_selfPowerGood)
                    DEBUGLOG("power supply good\n");
                else
                    DEBUGLOG("no external power\n");
            }
            else
            {
                DEBUGLOG("%s: Hub attached - Bus powered\n", getName());
            }

        }
        startExternal = (_busPowerGood || _selfPowerGood);
        if( !startExternal )
        {	/* not plugged in or bus powered on a bus powered hub */
            err = kIOReturnNoPower;
            DEBUGLOG("%s: insufficient power to turn on ports\n", getName());
            if(!_busPowered)
            {
                /* may be able to turn on compound devices */
                break;	/* Now what ?? */
            }
        }
    } while (false);

    return(err == kIOReturnSuccess);
}


bool IOUSBHub::AllocatePortMemory(void)
{
    IOUSBHubPort 	*port;
    UInt32		power;
    int 		portMask = 2;
    int 		portByte = 0;
    int			currentPort;


    _ports = (IOUSBHubPort **)
    		IOMalloc(sizeof(IOUSBHubPort *) * _hubDescriptor.numPorts);
    if (!_ports)
        return(false);

    for (currentPort = 1; currentPort <= _hubDescriptor.numPorts; currentPort++)
    {
        /* determine if the port is a captive port */
        if ((_hubDescriptor.removablePortFlags[portByte] & portMask) != 0)
        {
            power = _powerForCaptive;
        }
        else
        {
            power = _selfPowerGood ? kUSB500mAAvailable : kUSB100mAAvailable;
        }

        port = new IOUSBHubPort;
        port->init(self, currentPort, power);
        _ports[currentPort-1] = port;

        portMask <<= 1;
        if(portMask > 0x80)
        {
            portMask = 1;
            portByte++;
        }
    }
    
    return(true);
}


bool IOUSBHub::StartPorts(void)
{
    IOReturn		err = kIOReturnSuccess;
    IOUSBHubPort 	*port;
    int			currentPort;
    //IOThread 	     	ioThread;


#if (DEBUGGING_LEVEL > 0)
    DEBUGLOG("%s: starting ports (%d)\n", getName(), _hubDescriptor.numPorts);
#endif

    for (currentPort = 1; currentPort <= _hubDescriptor.numPorts; currentPort++)
    {
        port = _ports[currentPort-1];
	if (port)
            err = port->start();

        // Temporarily fork off a thread for each port.  Each port could
        // potentially call addDevice which will generate hub events.
        //ioThread = IOCreateThread(callStart, port);
        //if (ioThread == NULL)
        //{
        //    err = kIOReturnNoMemory;
        //    break;
        //}
    }

    // Ignore any error for now.  Maybe the hub would want to know
    // if all ports failed, but if at least one succeeds, then we
    // should continue.
    return(true);
}

bool IOUSBHub::StopPorts(void)
{
    IOUSBHubPort 	*port;
    int			currentPort;


#if (DEBUGGING_LEVEL > 0)
    DEBUGLOG("%s: stopping ports (%d)\n", getName(), _hubDescriptor.numPorts);
#endif

    if( _ports) for (currentPort = 1; currentPort <= _hubDescriptor.numPorts; currentPort++)
    {
        port = _ports[currentPort-1];
	if (port) {
            port->stop();
            delete port;
            _ports[currentPort-1] = 0;
	}
    }

    IOFree(_ports, sizeof(IOUSBHubPort *) * _hubDescriptor.numPorts);
    _ports = 0;
    return(true);
}

const UInt8 * IOUSBHub::getStatusChanged(void)
{
    IOReturn		err = kIOReturnSuccess;
    const UInt8 *	status = NULL;

    _buffer->setLength(8);

    do
    {
        if ((err = _interruptPipe->read(_buffer)))
        {
            DEBUGLOG("%s: error 0x%x reading interrupt pipe.\n", getName(), err);
            break;
        }

        if (err == kIOReturnNotResponding)
        {
            // This probably means the device was unplugged.
            break;
        }
        else if (err != kIOReturnSuccess)
        {
            // We should handle other errors more intelligently, but
            // for now just return and assume the error is recoverable.
            //kprintf("%s: error %d reading interrupt pipe\n", getName(), err);
            break;
        }
        status = (const UInt8 *)_buffer->getBytesNoCopy();
    } while (false);
    return status;
}

bool IOUSBHub::StartHandler(void)
{
    IOReturn	err = kIOReturnSuccess;
    const UInt8	*statusChangedBitmap;
    int 	portMask;
    int 	portByte;
    int 	portIndex;
    IOUSBHubPort *port;

    do
    {
        portMask = 2;
        portByte = 0;
        statusChangedBitmap = getStatusChanged();
        if (statusChangedBitmap == 0)
        {
            DEBUGLOG("%s: no data returned from int pipe\n", getName());
            break;
        }

#if (DEBUGGING_LEVEL > 0)
        DEBUGLOG("read returned 0x%x (%d)\n", statusChangedBitmap[portByte],
                 _buffer->getLength());
#endif

        for (portIndex = 1; portIndex <=_hubDescriptor.numPorts; portIndex++)
        {
            if ((statusChangedBitmap[portByte] & portMask) != 0)
            {
                port = _ports[portIndex-1];
                port->statusChanged();
            }

            portMask <<= 1;
            if (portMask > 0x80)
            {
                portMask = 1;
                portByte++;
            }
        }

        /* hub status changed */
        if ((statusChangedBitmap[0] & 1) != 0)
        {	
            statusChanged();
        }

    } while (true);

    DEBUGLOG("%s: error %d.  Exiting Hub Handler\n", getName(), err);

    _device->close(this);
    
    return(false);
}


bool IOUSBHub::statusChanged(void)
{
    IOReturn	err = kIOReturnSuccess;

    do
    {
        if ((err = GetHubStatus(&_hubStatus)))
        {
            fatalError(err, "get status (first in hub status change)");
            break;            
        }
        _hubStatus.statusFlags = USBToHostWord(_hubStatus.statusFlags);
        _hubStatus.changeFlags = USBToHostWord(_hubStatus.changeFlags);

#if (DEBUGGING_LEVEL > 0)
        DEBUGLOG("%s: hub status = %x/%x\n", getName(),
                 _hubStatus.statusFlags, _hubStatus.changeFlags);
#endif

        if (_hubStatus.changeFlags & kHubLocalPowerStatusChange)
        {
#if (DEBUGGING_LEVEL > 0)
            DEBUGLOG("%s: Local Power Status Change detected\n", getName());
#endif
            if ((err = ClearHubFeature(kHubLocalPowerStatusChange)))
            {
                fatalError(err, "clear hub power status feature");
                break;
            }
            if ((err = GetHubStatus(&_hubStatus)))
            {
                fatalError(err, "get status (second in hub status change)");
                break;
            }
            _hubStatus.statusFlags = USBToHostWord(_hubStatus.statusFlags);
            _hubStatus.changeFlags = USBToHostWord(_hubStatus.changeFlags);
        }

        if (_hubStatus.changeFlags & kHubOverCurrentIndicatorChange)
        {
#if (DEBUGGING_LEVEL > 0)
            DEBUGLOG("%s: OverCurrent Indicator Change detected\n", getName());
#endif
            if ((err =
                 ClearHubFeature(kHubOverCurrentIndicatorChange)))
            {
                fatalError(err, "clear hub over current feature");
                break;
            }
            if ((err = GetHubStatus(&_hubStatus)))
            {
                fatalError(err, "get status (second in hub status change)");
                break;
            }
            _hubStatus.statusFlags = USBToHostWord(_hubStatus.statusFlags);
            _hubStatus.changeFlags = USBToHostWord(_hubStatus.changeFlags);
        }

        //DEBUGLOG("%s: calling delay reentering poll loop\n", getName());
        //USBDelay(10);  //FIXME

    } while(false);

    return(err == kIOReturnSuccess);
}

static ErrataListEntry	errataList[] = {

/* For the Cherry 4 port KB, From Cherry:
We use the bcd_releasenumber-highbyte for hardware- and the lowbyte for
firmwarestatus. We have 2 different for the hardware 03(eprom) and
06(masked microcontroller). Firmwarestatus is 05 today.
So high byte can be 03 or 06 ----  low byte can be 01, 02, 03, 04, 05

Currently we are working on a new mask with the new descriptors. The
firmwarestatus will be higher than 05.
*/
      {0x046a, 0x003, 0x0301, 0x0305, kErrataCaptiveOK}, // Cherry 4 port KB
      {0x046a, 0x003, 0x0601, 0x0605, kErrataCaptiveOK}  // Cherry 4 port KB
};

#define errataListLength (sizeof(errataList)/sizeof(ErrataListEntry))

UInt32 GetErrataBits(IOUSBDeviceDescriptor *desc)
{
      UInt16		vendID, deviceID, revisionID;
      ErrataListEntry	*entryPtr;
      UInt32		i, errata = 0;

      // get this chips vendID, deviceID, revisionID
      vendID = USBToHostWord(desc->vendor);
      deviceID = USBToHostWord(desc->product);
      revisionID = USBToHostWord(desc->devRel);

      for(i=0, entryPtr = errataList; i < errataListLength; i++, entryPtr++)
      {
          if (vendID == entryPtr->vendID
              && deviceID == entryPtr->deviceID
              && revisionID >= entryPtr->revisionLo
              && revisionID <= entryPtr->revisionHi)
          {
              errata |= entryPtr->errata;  // we match, add this errata to our list
          }
      }
      return(errata);
}

void IOUSBHub::fatalError(IOReturn err, char *str)
{
    DEBUGLOG("IOUSBHub: Error %d: %s\n", err, str);
}

IOReturn IOUSBHub::GetHubDescriptor(IOUSBHubDescriptor *desc)
{
    IOReturn	err = kIOReturnSuccess;
    IOUSBDevRequest	request;

#if (DEBUGGING_LEVEL > 0)
    DEBUGLOG("*********** GET HUB DESCRIPTOR ***********\n");
#endif

    if (!desc) return (kIOReturnBadArgument);

    request.rqDirection = kUSBIn;
    request.rqType = kUSBClass;
    request.rqRecipient = kUSBDevice;
    request.bRequest = kUSBRqGetDescriptor;
    OSWriteLittleInt16(&request.wValue, 0, kUSBHubDescriptorType);
    OSWriteLittleInt16(&request.wIndex, 0, 0);
    OSWriteLittleInt16(&request.wLength, 0, sizeof(IOUSBHubDescriptor));
    request.pData = desc;

    err = _device->deviceRequest(&request);

    if (err)
    {
        /*
         * Is this a bogus hub?  Some hubs require 0 for the descriptor type
         * to get their device descriptor.  This is a bug, but it's actually
         * spec'd out in the USB 1.1 docs.
         */
        OSWriteLittleInt16(&request.wValue, 0, 0);
        OSWriteLittleInt16(&request.wLength, 0, sizeof(IOUSBHubDescriptor));
        err = _device->deviceRequest(&request);
    }

    if (err)
        IOLog("%s: error getting hub descriptor. err=%d\n", getName(), err);

    return(err);
}

IOReturn IOUSBHub::GetHubStatus(IOUSBHubStatus *status)
{
    IOReturn		err = kIOReturnSuccess;
    IOUSBDevRequest	request;

#if (DEBUGGING_LEVEL > 0)
    DEBUGLOG("************* GET HUB STATUS *************\n");
#endif

    request.rqDirection = kUSBIn;
    request.rqType = kUSBClass;
    request.rqRecipient = kUSBDevice;
    request.bRequest = kUSBRqGetStatus;
    OSWriteLittleInt16(&request.wValue, 0, 0);
    OSWriteLittleInt16(&request.wIndex, 0, 0);
    OSWriteLittleInt16(&request.wLength, 0, sizeof(IOUSBHubStatus));
    request.pData = status;

    err = _device->deviceRequest(&request);

    if (err)
        IOLog("%s: error getting hub status. err=%d\n", getName(), err);

    return(err);
}

IOReturn IOUSBHub::GetPortState(UInt8 *state, UInt16 port)
{
    IOReturn		err = kIOReturnSuccess;
    IOUSBDevRequest	request;

#if (DEBUGGING_LEVEL > 0)
    DEBUGLOG("************* GET PORT STATE *************\n");
#endif

    request.rqDirection = kUSBIn;
    request.rqType = kUSBClass;
    request.rqRecipient = kUSBOther;
    request.bRequest = kUSBRqGetState;
    OSWriteLittleInt16(&request.wValue, 0, 0);
    OSWriteLittleInt16(&request.wIndex, 0, port);
    OSWriteLittleInt16(&request.wLength, 0, sizeof(*state));
    request.pData = state;

    err = _device->deviceRequest(&request);

    if (err)
        IOLog("%s: error getting hub state. err=%d\n", getName(), err);

    return(err);
}

IOReturn IOUSBHub::ClearHubFeature(UInt16 feature)
{
    IOReturn		err = kIOReturnSuccess;
    IOUSBDevRequest	request;

#if (DEBUGGING_LEVEL > 0)
    DEBUGLOG("********** CLEAR HUB FEATURE %d **********\n", feature);
#endif

    request.rqDirection = kUSBOut;
    request.rqType = kUSBClass;
    request.rqRecipient = kUSBDevice;
    request.bRequest = kUSBRqClearFeature;
    OSWriteLittleInt16(&request.wValue, 0, feature);
    OSWriteLittleInt16(&request.wIndex, 0, 0);
    OSWriteLittleInt16(&request.wLength, 0, 0);
    OSWriteLittleInt16(&request.pData, 0, 0);

    err = _device->deviceRequest(&request);

    if (err)
        IOLog("%s: error clearing hub feature. err=%d\n", getName(), err);

    return(err);
}

IOReturn IOUSBHub::GetPortStatus(IOUSBHubPortStatus *status, UInt16 port)
{
    IOReturn	err = kIOReturnSuccess;
    IOUSBDevRequest	request;

#if (DEBUGGING_LEVEL > 0)
    DEBUGLOG("*********** GET PORT %d STATUS ************\n", port);
#endif

    request.rqDirection = kUSBIn;
    request.rqType = kUSBClass;
    request.rqRecipient = kUSBOther;
    request.bRequest = kUSBRqGetStatus;
    OSWriteLittleInt16(&request.wValue, 0, 0);
    OSWriteLittleInt16(&request.wIndex, 0, port);
    OSWriteLittleInt16(&request.wLength, 0, sizeof(IOUSBHubPortStatus));
    request.pData = status;

    err = _device->deviceRequest(&request);

    if (err)
        IOLog("%s: error getting port status. err=%d\n", getName(), err);

    // Get things the right way round.
    status->statusFlags = OSReadLittleInt16(&status->statusFlags, 0);
    status->changeFlags = OSReadLittleInt16(&status->changeFlags, 0);

    return(err);
}

IOReturn IOUSBHub::SetPortFeature(UInt16 feature, UInt16 port)
{
    IOReturn		err = kIOReturnSuccess;
    IOUSBDevRequest	request;

#if (DEBUGGING_LEVEL > 0)
    DEBUGLOG("********* SET PORT %d FEATURE %d **********\n", port, feature);
#endif

    request.rqDirection = kUSBOut;
    request.rqType = kUSBClass;
    request.rqRecipient = kUSBOther;
    request.bRequest = kUSBRqSetFeature;
    OSWriteLittleInt16(&request.wValue, 0, feature);
    OSWriteLittleInt16(&request.wIndex, 0, port);
    OSWriteLittleInt16(&request.wLength, 0, 0);
    request.pData = 0;

    err = _device->deviceRequest(&request);

    if (err)
        IOLog("%s: error setting port feature. err=%d\n", getName(), err);

    return(err);
}

IOReturn IOUSBHub::ClearPortFeature(UInt16 feature, UInt16 port)
{
    IOReturn		err = kIOReturnSuccess;
    IOUSBDevRequest	request;

#if (DEBUGGING_LEVEL > 0)
    DEBUGLOG("******** CLEAR PORT %d FEATURE %d *********\n", port, feature);
#endif

    request.rqDirection = kUSBOut;
    request.rqType = kUSBClass;
    request.rqRecipient = kUSBOther;
    request.bRequest = kUSBRqClearFeature;
    OSWriteLittleInt16(&request.wValue, 0, feature);
    OSWriteLittleInt16(&request.wIndex, 0, port);
    OSWriteLittleInt16(&request.wLength, 0, 0);
    request.pData = 0;

    err = _device->deviceRequest(&request);

    if (err)
        IOLog("%s: error clearing port feature. err=%d\n", getName(), err);

    return(err);
}

void IOUSBHub::PrintHubDescriptor(IOUSBHubDescriptor *desc)
{
    int i = 0;
    char *characteristics[] =
        { "ppsw", "nosw", "comp", "ppoc", "nooc", 0 };


    if (desc->length == 0) return;

    DEBUGLOG("hub descriptor: (%d bytes)\n", desc->length);
    DEBUGLOG("\thubType = %d\n", desc->hubType);
    DEBUGLOG("\tnumPorts = %d\n", desc->numPorts);
    DEBUGLOG("\tcharacteristics = %x ( ",
                                USBToHostWord(desc->characteristics));
    do
    {
        if (USBToHostWord(desc->characteristics) & (1 << i))
            DEBUGLOG("%s ", characteristics[i]);
    } while (characteristics[++i]);
    DEBUGLOG(")\n");
    DEBUGLOG("\tpowerOnToGood = %d ms\n", desc->powerOnToGood * 2);
    DEBUGLOG("\thubCurrent = %d\n", desc->hubCurrent);
    DEBUGLOG("\tremovablePortFlags = %x %x\n", &desc->removablePortFlags[1], &desc->removablePortFlags[0]);
    DEBUGLOG("\tpwrCtlPortFlags    = %x %x\n", &desc->pwrCtlPortFlags[1], &desc->removablePortFlags[0]);
}