#include <libkern/OSByteOrder.h>
#include <IOKit/IOService.h>
#include <IOKit/IOKitKeys.h>
#include <IOKit/usb/IOUSBController.h>
#include <IOKit/usb/IOUSBControllerV2.h>
#include <IOKit/usb/IOUSBControllerV3.h>
#include <IOKit/usb/USBHub.h>
#include <IOKit/usb/IOUSBDevice.h>
#include <IOKit/usb/IOUSBPipeV2.h>
#include <IOKit/usb/IOUSBNub.h>
#include <IOKit/usb/IOUSBLog.h>
#include "IOUSBInterfaceUserClient.h"
#include "USBTracepoints.h"
#define super IOUSBPipeV2
#define _DEVICE _expansionData->_device
#define _CORRECTSTATUS _expansionData->_correctStatus
#define _SPEED _expansionData->_speed
#define _INTERFACE _expansionData->_interface
#define _CROSSENDIANCOMPATIBLE _expansionData->_crossEndianCompatible
#define _LOCATIONID _expansionData->_locationID
#define _OUTOFSPECMPSOK _status // if non-zero, then we should ignore an out of spec MPS
#ifndef IOUSBPipe_USE_KPRINTF
#define IOUSBPIPE_USE_KPRINTF 0
#endif
#if IOUSBPipe_USE_KPRINTF
#undef USBLog
#undef USBError
void kprintf(const char *format, ...) __attribute__((format(printf, 1, 2)));
#define USBLog( LEVEL, FORMAT, ARGS... ) if ((LEVEL) <= IOUSBPipe_USE_KPRINTF) { kprintf( FORMAT "\n", ## ARGS ) ; }
#define USBError( LEVEL, FORMAT, ARGS... ) { kprintf( FORMAT "\n", ## ARGS ) ; }
#endif
OSDefineMetaClassAndStructors(IOUSBPipeV2, IOUSBPipe)
#pragma mark Intializers
bool
IOUSBPipeV2::InitToEndpoint(const IOUSBEndpointDescriptor *ed, IOUSBSuperSpeedEndpointCompanionDescriptor *sscd, UInt8 speed, USBDeviceAddress address, IOUSBController * controller, IOUSBDevice * device, IOUSBInterface * interface)
{
IOReturn err;
IOUSBControllerV3 *controllerV3;
USBTrace_Start( kUSBTPipe, kTPPipeInitToEndpoint, (uintptr_t)this, (uintptr_t)ed, speed, address);
if ( !super::init() || ed == 0)
return (false);
if (!_expansionData)
{
_expansionData = (ExpansionData *)IOMalloc(sizeof(ExpansionData));
if (!_expansionData)
return false;
bzero(_expansionData, sizeof(ExpansionData));
}
_controller = controller;
controllerV3 = OSDynamicCast(IOUSBControllerV3, _controller);
if ( controllerV3 == NULL )
{
if(sscd != NULL)
{
USBError(1,"IOUSBPipeV2[%p]:InitToEndpoint -- Requested a pipe with stream ID, but this IOUSBController does not support it", this);
return false;
}
}
_descriptor = ed;
_sscd = sscd;
_endpoint.number = ed->bEndpointAddress & kUSBPipeIDMask;
_endpoint.transferType = ed->bmAttributes & 0x03;
if (_endpoint.transferType == kUSBControl)
_endpoint.direction = kUSBAnyDirn;
else
_endpoint.direction = (ed->bEndpointAddress & 0x80) ? kUSBIn : kUSBOut;
_endpoint.maxPacketSize = mungeMaxPacketSize(USBToHostWord(ed->wMaxPacketSize));
_endpoint.interval = ed->bInterval;
if (_sscd != NULL)
{
_maxBurst = _sscd->bMaxBurst;
_bytesPerInterval = _sscd->wBytesPerInterval;
if (_endpoint.transferType == kUSBBulk)
{
int max;
max = (_sscd->bmAttributes & 0x1f);
_configuredStreams = 0;
if(max > 0)
{
UInt32 maxSupported;
_maxStream = (1 << max)-1; if(_maxStream > kUSBMaxStream)
{
_maxStream = kUSBMaxStream;
}
maxSupported = controllerV3->UIMMaxSupportedStream();
if( (_maxStream > 0) && (maxSupported == 0) )
{
USBLog(2,"IOUSBPipeV2[%p]:InitToEndpoint -- Requested a pipe with %d streams, but this IOUSBController does not support streams", this, (uint32_t)_maxStream);
_maxStream = 0;
return false;
}
if(_maxStream > maxSupported)
{
USBLog(2,"IOUSBPipeV2[%p]:InitToEndpoint -- Requested a pipe with %d streams, but this IOUSBController only supports %d streams", this, (uint32_t)_maxStream, (uint32_t)maxSupported);
_maxStream = maxSupported;
}
}
USBLog(3, "IOUSBPipeV2[%p]::InitToEndpoint: SuperSpeed Bulk pipe maxStreams: %d", this, (uint32_t)_maxStream);
}
if (_endpoint.transferType == kUSBControl)
{
_maxBurst = 0;
}
if (_endpoint.transferType == kUSBIsoc)
{
_mult = (_sscd->bmAttributes & 0x3);
}
}
_address = address;
_SPEED = speed;
_DEVICE = device;
_INTERFACE = interface;
if ( _DEVICE )
{
OSNumber * location = OSDynamicCast(OSNumber, _DEVICE->getProperty(kUSBDevicePropertyLocationID));
if (location)
{
_LOCATIONID = location->unsigned32BitValue();
}
}
USBTrace(kUSBTPipe, kTPPipeInitToEndpoint, (uintptr_t)this, (uintptr_t)_controller, _endpoint.transferType, _endpoint.maxPacketSize);
if ( (_SPEED == kUSBDeviceSpeedHigh) && (_endpoint.transferType == kUSBBulk) && (_endpoint.maxPacketSize != 512) )
{
if ( _OUTOFSPECMPSOK == 0 )
{
USBError(1, "Endpoint 0x%x of the USB device \"%s\" at location 0x%x: converting Bulk MPS from %d to 512 (USB 2.0 Spec section 5.8.3)", ed->bEndpointAddress, _DEVICE ? _DEVICE->getName() : "Unnamed", (uint32_t)_LOCATIONID, _endpoint.maxPacketSize);
_endpoint.maxPacketSize = 512;
}
else
{
USBLog(5, "IOUSBPipeV2[%p]::InitToEndpoint: High Speed Bulk pipe with %d MPS, but property says we should still use it.", this, _endpoint.maxPacketSize);
}
}
if( (_maxStream == 0) && (_maxBurst == 0) )
{
err = _controller->OpenPipe(_address, speed, &_endpoint);
}
else
{
err = controllerV3->OpenPipe(_address, speed, &_endpoint, _maxStream, _maxBurst);
}
if ((err == kIOReturnNoBandwidth) && (_endpoint.transferType == kUSBIsoc))
{
USBError(1,"There is not enough USB isochronous bandwidth to allow the device \"%s\" at location 0x%x to function in its current configuration (requested %d bytes for endpoint 0x%x )", _DEVICE ? _DEVICE->getName() : "Unnamed", (uint32_t)_LOCATIONID, _endpoint.maxPacketSize, ed->bEndpointAddress);
_endpoint.maxPacketSize = 0;
USBLog(6, "IOUSBPipeV2[%p]::InitToEndpoint can't get bandwidth for isoc pipe - creating 0 bandwidth pipe", this);
err = _controller->OpenPipe(_address, speed, &_endpoint);
}
if ( err != kIOReturnSuccess)
{
USBLog(3,"IOUSBPipeV2[%p]::InitToEndpoint Could not create pipe for endpoint (Addr: %d, numb: %d, type: %d). Error 0x%x", this, _address, _endpoint.number, _endpoint.transferType, err);
return false;
}
USBTrace_End( kUSBTPipe, kTPPipeInitToEndpoint, (uintptr_t)this, 0, 0, 0);
return true;
}
IOUSBPipeV2 *
IOUSBPipeV2::ToEndpoint(const IOUSBEndpointDescriptor *ed, IOUSBSuperSpeedEndpointCompanionDescriptor *sscd, IOUSBDevice * device, IOUSBController *controller, IOUSBInterface * interface)
{
OSObject * propertyObj = NULL;
OSBoolean * boolObj = NULL;
IOUSBPipeV2 * me = new IOUSBPipeV2;
USBLog(6, "IOUSBPipeV2[%p]::ToEndpoint device %p(%s), interface %p, ep: 0x%x,0x%x,%d,%d", me, device, device->getName(), interface, ed->bEndpointAddress, ed->bmAttributes, ed->wMaxPacketSize, ed->bInterval);
if ( me && interface)
{
propertyObj = interface->copyProperty(kUSBOutOfSpecMPSOK);
boolObj = OSDynamicCast( OSBoolean, propertyObj );
if ( boolObj && boolObj->isTrue())
{
USBLog(6,"IOUSBPipeV2[%p]::ToEndpoint Device reports Out of spec MPS property and is TRUE", me);
me->_OUTOFSPECMPSOK = 0xFF;
}
if (propertyObj)
propertyObj->release();
}
if ( !me->InitToEndpoint(ed, sscd, device->GetSpeed(), device->GetAddress(), controller, device, interface) )
{
me->release();
return NULL;
}
if ( me->_INTERFACE )
{
OSObject * propertyObj = me->_INTERFACE->copyProperty(kIOUserClientCrossEndianCompatibleKey);
OSBoolean * boolObj = OSDynamicCast( OSBoolean, propertyObj );
if ( boolObj )
{
if (boolObj->isTrue() )
{
USBLog(6,"IOUSBPipeV2[%p]::ToEndpoint CrossEndianProperty exists and is TRUE", me);
me->_CROSSENDIANCOMPATIBLE = true;
}
else
{
USBLog(6,"IOUSBPipeV2[%p]::ToEndpoint CrossEndianProperty exists and is FALSE", me);
me->_CROSSENDIANCOMPATIBLE = false;
}
}
if (propertyObj)
propertyObj->release();
}
return me;
}
#pragma mark IOUSBPipeV2 State
IOReturn
IOUSBPipeV2::Abort(UInt32 streamID)
{
IOUSBControllerV3 * controllerV3;
USBLog(5,"IOUSBPipeV2[%p]::AbortPipe(0x%x)",this, (uint32_t)streamID);
if (_CORRECTSTATUS != 0)
{
USBLog(2, "IOUSBPipeV2[%p]::Abort setting status to 0", this);
}
_CORRECTSTATUS = 0;
controllerV3 = OSDynamicCast(IOUSBControllerV3, _controller);
if ( controllerV3 == NULL )
{
if(streamID != 0)
{
USBLog(2,"IOUSBPipeV2[%p]:Abort -- Requested an abort with stream ID, but this IOUSBController does not support it", this);
return kIOReturnUnsupported;
}
}
if(streamID == 0)
{
USBLog(5, "IOUSBPipeV2[%p]::Abort(streamID) - bad argument: stream ID zero (%d)", this, (uint32_t)streamID);
return kIOReturnBadArgument;
}
if(streamID != kUSBAllStreams)
{
if(_endpoint.transferType != kUSBBulk)
{
USBLog(5, "IOUSBPipeV2[%p]::Abort(streamID) - bad argument: abort stream (%d) on non bulk pipe", this, (uint32_t)streamID);
return kIOReturnBadArgument;
}
if(streamID > _maxStream)
{
USBLog(5, "IOUSBPipeV2[%p]::Abort(streamID) - bad argument: stream ID greater than max (%d > %d)", this, (uint32_t)streamID, (uint32_t)_maxStream);
return kIOReturnBadArgument;
}
}
if ( streamID != 0 )
{
return controllerV3->AbortPipe(streamID, _address, &_endpoint);
}
else
{
return _controller->AbortPipe(_address, &_endpoint);
}
}
#pragma mark Bulk Read
IOReturn
IOUSBPipeV2::Read(UInt32 streamID, IOMemoryDescriptor *buffer, UInt32 noDataTimeout, UInt32 completionTimeout, IOByteCount reqCount, IOUSBCompletion *completion, IOByteCount *bytesRead)
{
IOReturn err = kIOReturnSuccess;
IOUSBControllerV3 * controllerV3;
controllerV3 = OSDynamicCast(IOUSBControllerV3, _controller);
if ( controllerV3 == NULL )
{
if(streamID != 0)
{
USBLog(2,"IOUSBPipeV2[%p]:Read -- Requested a Read with stream ID, but this IOUSBController does not support it", this);
return kIOReturnUnsupported;
}
}
if(streamID != 0)
{
if(_endpoint.transferType != kUSBBulk)
{
USBLog(5, "IOUSBPipeV2[%p]::Read - bad arguments: stream (%d) read on non bulk pipe", this, (uint32_t)streamID);
return kIOReturnBadArgument;
}
if(streamID > _maxStream)
{
USBLog(5, "IOUSBPipeV2[%p]::Read - bad arguments: stream ID greater than max (%d > %d)", this, (uint32_t)streamID, (uint32_t)_maxStream);
return kIOReturnBadArgument;
}
}
USBLog(7, "IOUSBPipeV2[%p]::Read (addr %d:%d type %d) - reqCount = %qd", this, _address, _endpoint.number , _endpoint.transferType, (uint64_t)reqCount);
USBTrace_Start( kUSBTPipe, kTPBulkPipeRead, _address, _endpoint.number , _endpoint.transferType, reqCount );
if ((_endpoint.transferType != kUSBBulk) && (noDataTimeout || completionTimeout))
{
USBLog(5, "IOUSBPipeV2[%p]::Read - bad arguments: (EP type: %d != kUSBBulk(%d)) && ( dataTimeout: %d || completionTimeout: %d)", this, _endpoint.transferType, kUSBBulk, (uint32_t)noDataTimeout, (uint32_t)completionTimeout);
return kIOReturnBadArgument;
}
if (!buffer || (buffer->getLength() < reqCount))
{
USBLog(5, "IOUSBPipeV2[%p]::Write - bad buffer: (buffer %p) || ( length %qd < reqCount %qd)", this, buffer, buffer ? (uint64_t)buffer->getLength() : 0, (uint64_t)reqCount);
return kIOReturnBadArgument;
}
if (_CORRECTSTATUS == kIOUSBPipeStalled)
{
USBLog(2, "IOUSBPipeV2[%p]::Read - invalid read on a stalled pipe", this);
return kIOUSBPipeStalled;
}
if (completion == NULL)
{
IOUSBCompletion tap;
if (bytesRead)
*bytesRead = reqCount;
tap.target = NULL;
tap.action = &IOUSBSyncCompletion;
tap.parameter = bytesRead;
if(streamID != 0)
{
err = controllerV3->Read(streamID, buffer, _address, &_endpoint, &tap, noDataTimeout, completionTimeout, reqCount);
}
else
{
err = _controller->Read(buffer, _address, &_endpoint, &tap, noDataTimeout, completionTimeout, reqCount);
}
if (err != kIOReturnSuccess)
{
if (err && (err != kIOUSBTransactionTimeout))
{
USBLog(2, "IOUSBPipe[%p]::Read(sync) returned 0x%x (%s) - stalling pipe", this, err, USBStringFromReturn(err));
_CORRECTSTATUS = kIOUSBPipeStalled;
}
}
}
else
{
if (completion->action == NULL)
{
USBLog(1, "IOUSBPipeV2[%p]::Read - completion has NULL action - returning kIOReturnBadArgument(%p)", this, (void*)kIOReturnBadArgument);
USBTrace(kUSBTPipe, kTPBulkPipeRead, (uintptr_t)this, kIOReturnBadArgument, 0, 1 );
return kIOReturnBadArgument;
}
if(streamID != 0)
{
err = controllerV3->Read(streamID, buffer, _address, &_endpoint, completion, noDataTimeout, completionTimeout, reqCount);
}
else
{
err = _controller->Read(buffer, _address, &_endpoint, completion, noDataTimeout, completionTimeout, reqCount);
}
}
if (err == kIOUSBPipeStalled)
{
USBLog(2, "IOUSBPipeV2[%p]::Read - controller returned stalled pipe, changing status", this);
_CORRECTSTATUS = kIOUSBPipeStalled;
}
USBLog(7, "IOUSBPipeV2[%p]::Read (addr %d:%d type %d) - reqCount = %qd, returning: 0x%x", this, _address, _endpoint.number , _endpoint.transferType, (uint64_t)reqCount, err);
USBTrace_End( kUSBTPipe, kTPBulkPipeRead, (uintptr_t)this, _address, _endpoint.number, err);
return(err);
}
#pragma mark Bulk Write
IOReturn
IOUSBPipeV2::Write(UInt32 streamID, IOMemoryDescriptor *buffer, UInt32 noDataTimeout, UInt32 completionTimeout, IOByteCount reqCount, IOUSBCompletion *completion)
{
IOReturn err = kIOReturnSuccess;
IOUSBControllerV3 * controllerV3;
controllerV3 = OSDynamicCast(IOUSBControllerV3, _controller);
if ( controllerV3 == NULL )
{
if(streamID != 0)
{
USBLog(2,"IOUSBPipeV2[%p]:write -- Requested a stream write, but this IOUSBController does not support it", this);
return kIOReturnUnsupported;
}
}
if(streamID != 0)
{
if(_endpoint.transferType != kUSBBulk)
{
USBLog(5, "IOUSBPipeV2[%p]::Write - bad arguments: stream (%d) read on non bulk pipe", this, (uint32_t)streamID);
return kIOReturnBadArgument;
}
if(streamID > _maxStream)
{
USBLog(5, "IOUSBPipeV2[%p]::Write - bad arguments: stream ID greater than max (%d > %d)", this, (uint32_t)streamID, (uint32_t)_maxStream);
return kIOReturnBadArgument;
}
}
USBLog(7, "IOUSBPipeV2[%p]::Write (addr %d:%d type %d) - reqCount = %qd", this, _address, _endpoint.number , _endpoint.transferType, (uint64_t)reqCount);
USBTrace_Start( kUSBTPipe, kTPBulkPipeWrite, _address, _endpoint.number , _endpoint.transferType, reqCount );
if ((_endpoint.transferType != kUSBBulk) && (noDataTimeout || completionTimeout))
{
USBLog(5, "IOUSBPipeV2[%p]::Write - bad arguments: (EP type: %d != kUSBBulk(%d)) && ( dataTimeout: %d || completionTimeout: %d)", this, _endpoint.transferType, kUSBBulk, (uint32_t)noDataTimeout, (uint32_t)completionTimeout);
return kIOReturnBadArgument;
}
if (!buffer || (buffer->getLength() < reqCount))
{
USBLog(5, "IOUSBPipeV2[%p]::Write - bad buffer: (buffer %p) || ( length %qd < reqCount %qd)", this, buffer, buffer ? (uint64_t)buffer->getLength() : 0, (uint64_t)reqCount);
return kIOReturnBadArgument;
}
if (_CORRECTSTATUS == kIOUSBPipeStalled)
{
USBLog(2, "IOUSBPipeV2[%p]::Write - invalid write on a stalled pipe", this);
return kIOUSBPipeStalled;
}
if (completion == NULL)
{
IOUSBCompletion tap;
tap.target = NULL;
tap.action = &IOUSBSyncCompletion;
tap.parameter = NULL;
err = _controller->Write(buffer, _address, &_endpoint, &tap, noDataTimeout, completionTimeout, reqCount);
if (err != kIOReturnSuccess)
{
if (err && (err != kIOUSBTransactionTimeout))
{
USBLog(2, "IOUSBPipe[%p]::Write(sync) returned 0x%x (%s) - stalling pipe", this, err, USBStringFromReturn(err));
_CORRECTSTATUS = kIOUSBPipeStalled;
}
}
}
else
{
if (completion->action == NULL)
{
USBLog(1, "IOUSBPipeV2[%p]::Write - completion has NULL action - returning kIOReturnBadArgument(%p)", this, (void*)kIOReturnBadArgument);
USBTrace(kUSBTPipe, kTPBulkPipeWrite, (uintptr_t)this, kIOReturnBadArgument, 0, 0 );
return kIOReturnBadArgument;
}
if ( streamID != 0 )
{
err = controllerV3->Write(streamID, buffer, _address, &_endpoint, completion, noDataTimeout, completionTimeout, reqCount);
}
else
{
err = _controller->Write(buffer, _address, &_endpoint, completion, noDataTimeout, completionTimeout, reqCount);
}
}
if (err == kIOUSBPipeStalled)
{
USBLog(2, "IOUSBPipeV2[%p]::Write - controller returned stalled pipe, changing status", this);
_CORRECTSTATUS = kIOUSBPipeStalled;
}
USBTrace_End( kUSBTPipe, kTPBulkPipeWrite, (uintptr_t)this, _address, _endpoint.number, err);
USBLog(7, "IOUSBPipeV2[%p]::Write (addr %d:%d type %d) - reqCount = %qd, returning 0x%x", this, _address, _endpoint.number , _endpoint.transferType, (uint64_t)reqCount, err);
return err;
}
IOReturn
IOUSBPipeV2::CreateStreams(UInt32 maxStreams)
{
IOUSBControllerV3 * controllerV3;
IOReturn ret;
controllerV3 = OSDynamicCast(IOUSBControllerV3, _controller);
if ( controllerV3 == NULL )
{
USBLog(2,"IOUSBPipeV2[%p]:CreateStreams -- Requested stream creation, but this IOUSBController does not support it", this);
return kIOReturnUnsupported;
}
ret = controllerV3->CreateStreams(_address, _endpoint.number, _endpoint.direction, maxStreams);
if(ret == kIOReturnSuccess)
{
_configuredStreams = maxStreams;
}
return(ret);
}
#pragma mark Accessors
UInt32
IOUSBPipeV2::SupportsStreams(void)
{
return(_maxStream);
}
UInt32
IOUSBPipeV2::GetConfiguredStreams(void)
{
if(_configuredStreams != 0)
{
USBLog(2,"IOUSBPipeV2[%p]:GetConfiguredStreams -- fn:%d, %d, %d - Configured streams: %d", this, _address, (uint32_t)_endpoint.number, _endpoint.direction, (uint32_t)_configuredStreams);
}
return(_configuredStreams);
}
UInt8
IOUSBPipeV2::GetMaxBurst(void)
{
return _maxBurst;
}
UInt8
IOUSBPipeV2::GetMult(void)
{
return _mult;
}
UInt16
IOUSBPipeV2::GetBytesPerInterval(void)
{
return _bytesPerInterval;
}
const IOUSBSuperSpeedEndpointCompanionDescriptor *
IOUSBPipeV2::GetSuperSpeedEndpointCompanionDescriptor()
{
return _sscd;
}
#pragma mark Padding Slots
OSMetaClassDefineReservedUnused(IOUSBPipeV2, 0);
OSMetaClassDefineReservedUnused(IOUSBPipeV2, 1);
OSMetaClassDefineReservedUnused(IOUSBPipeV2, 2);
OSMetaClassDefineReservedUnused(IOUSBPipeV2, 3);
OSMetaClassDefineReservedUnused(IOUSBPipeV2, 4);
OSMetaClassDefineReservedUnused(IOUSBPipeV2, 5);
OSMetaClassDefineReservedUnused(IOUSBPipeV2, 6);
OSMetaClassDefineReservedUnused(IOUSBPipeV2, 7);
OSMetaClassDefineReservedUnused(IOUSBPipeV2, 8);
OSMetaClassDefineReservedUnused(IOUSBPipeV2, 9);
OSMetaClassDefineReservedUnused(IOUSBPipeV2, 10);
OSMetaClassDefineReservedUnused(IOUSBPipeV2, 11);
OSMetaClassDefineReservedUnused(IOUSBPipeV2, 12);
OSMetaClassDefineReservedUnused(IOUSBPipeV2, 13);
OSMetaClassDefineReservedUnused(IOUSBPipeV2, 14);
OSMetaClassDefineReservedUnused(IOUSBPipeV2, 15);
OSMetaClassDefineReservedUnused(IOUSBPipeV2, 16);
OSMetaClassDefineReservedUnused(IOUSBPipeV2, 17);
OSMetaClassDefineReservedUnused(IOUSBPipeV2, 18);
OSMetaClassDefineReservedUnused(IOUSBPipeV2, 19);
OSMetaClassDefineReservedUnused(IOUSBPipeV2, 20);
OSMetaClassDefineReservedUnused(IOUSBPipeV2, 21);
OSMetaClassDefineReservedUnused(IOUSBPipeV2, 22);
OSMetaClassDefineReservedUnused(IOUSBPipeV2, 23);
OSMetaClassDefineReservedUnused(IOUSBPipeV2, 24);
OSMetaClassDefineReservedUnused(IOUSBPipeV2, 25);
OSMetaClassDefineReservedUnused(IOUSBPipeV2, 26);
OSMetaClassDefineReservedUnused(IOUSBPipeV2, 27);
OSMetaClassDefineReservedUnused(IOUSBPipeV2, 28);
OSMetaClassDefineReservedUnused(IOUSBPipeV2, 29);