#include <IOKit/IORegistryEntry.h>
#include <IOKit/IOLib.h>
#include "IOHIDElement.h"
#include "IOHIDDevice.h"
#include "IOHIDEventQueue.h"
#include "IOHIDParserPriv.h"
#define IsRange() \
(_usageMin != _usageMax)
#define IsArrayElement(flags) \
((flags & kHIDDataArrayBit) == kHIDDataArray)
#define IsArrayReportHandler(reportHandler) \
(reportHandler == _arrayReportHandler)
#define GetArrayItemIndex(sel) \
(sel - _logicalMin)
#define GetArrayItemSel(index) \
(index + _logicalMin)
#define super OSObject
OSDefineMetaClassAndStructors( IOHIDElement, OSObject )
bool IOHIDElement::init( IOHIDDevice * owner, IOHIDElementType type )
{
if ( ( super::init() != true ) || ( owner == 0 ) )
{
return false;
}
_owner = owner;
_type = type;
_reportSize = 0;
_reportCount = 1;
return true;
}
IOHIDElement *
IOHIDElement::buttonElement( IOHIDDevice * owner,
IOHIDElementType type,
HIDButtonCapabilitiesPtr button,
IOHIDElement * parent )
{
IOHIDElement * element = new IOHIDElement;
if ( ( button == 0 ) ||
( element == 0 ) ||
( element->init( owner, type ) == false ) )
{
if ( element ) element->release();
return 0;
}
element->_flags = button->bitField;
element->_reportStartBit = button->startBit;
element->_reportID = button->reportID;
element->_usagePage = button->usagePage;
element->_rangeIndex = 0;
element->_logicalMin = element->_physicalMin = 0;
element->_logicalMax = element->_physicalMax = 1;
if ( button->isRange )
{
element->_usageMin = button->u.range.usageMin;
element->_usageMax = button->u.range.usageMax;
}
else
{
element->_usageMin = button->u.notRange.usage;
element->_usageMax = button->u.notRange.usage;
}
if (IsArrayElement(element->_flags))
{
element->_logicalMin = element->_physicalMin = button->u.notRange.reserved2;
element->_logicalMax = element->_physicalMax = button->u.notRange.reserved3;
element->_reportBits = button->unitExponent;
element->_reportCount = button->units;
}
else
{
element->_reportBits = 1;
element->_units = button->units;
element->_unitExponent = button->unitExponent;
}
if ( ( parent && ( parent->addChildElement(element) == false ) )
|| ( owner->registerElement( element, &element->_cookie ) == false )
|| ( element->createSubElements() == false ))
{
element->release();
element = 0;
}
return element;
}
IOHIDElement *
IOHIDElement::valueElement( IOHIDDevice * owner,
IOHIDElementType type,
HIDValueCapabilitiesPtr value,
IOHIDElement * parent )
{
IOHIDElement * element = new IOHIDElement;
if ( ( value == 0 ) ||
( element == 0 ) ||
( element->init( owner, type ) == false ) )
{
if ( element ) element->release();
return 0;
}
element->_flags = value->bitField;
element->_reportBits = value->bitSize;
element->_reportCount = value->reportCount;
element->_reportStartBit = value->startBit;
element->_reportID = value->reportID;
element->_usagePage = value->usagePage;
element->_logicalMin = value->logicalMin;
element->_logicalMax = value->logicalMax;
element->_physicalMin = value->physicalMin;
element->_physicalMax = value->physicalMax;
element->_units = value->units;
element->_unitExponent = value->unitExponent;
element->_rangeIndex = 0;
if ( value->isRange )
{
element->_usageMin = value->u.range.usageMin;
element->_usageMax = value->u.range.usageMax;
}
else
{
element->_usageMin = value->u.notRange.usage;
element->_usageMax = value->u.notRange.usage;
}
if ( ( owner->registerElement( element, &element->_cookie ) == false )
|| ( ( parent && ( parent->addChildElement(element) == false ) ) )
|| ( element->createSubElements() == false ))
{
element->release();
element = 0;
}
return element;
}
IOHIDElement *
IOHIDElement::collectionElement( IOHIDDevice * owner,
IOHIDElementType type,
HIDCollectionNodePtr collection,
IOHIDElement * parent )
{
IOHIDElement * element = new IOHIDElement;
if ( ( collection == 0 ) ||
( element == 0 ) ||
( element->init( owner, type ) == false ) )
{
if ( element ) element->release();
return 0;
}
element->_usagePage = collection->collectionUsagePage;
element->_usageMin = collection->collectionUsage;
element->_usageMax = collection->collectionUsage;
if ( ( owner->registerElement( element, &element->_cookie ) == false )
|| ( ( parent && ( parent->addChildElement(element) == false ) ) ) )
{
element->release();
element = 0;
}
return element;
}
IOHIDElement * IOHIDElement::newSubElement( UInt16 rangeIndex ) const
{
IOHIDElement * element = new IOHIDElement;
if ( (element == 0 ) ||
(rangeIndex == 0) ||
( element->init( _owner, _type ) == false ) )
{
if ( element ) element->release();
return 0;
}
element->_flags = _flags;
element->_reportID = _reportID;
element->_usagePage = _usagePage;
element->_usageMin = _usageMin;
element->_usageMax = _usageMax;
element->_rangeIndex = rangeIndex;
if ( IsArrayElement(_flags) )
{
element->_reportBits = 1;
element->_logicalMin = element->_physicalMin = 0;
element->_logicalMax = element->_physicalMax = 1;
element->_reportStartBit = _reportStartBit;
}
else
{
element->_reportBits = _reportBits;
element->_reportStartBit = _reportStartBit + (rangeIndex * _reportBits);
element->_logicalMin = _logicalMin;
element->_logicalMax = _logicalMax;
element->_physicalMin = _physicalMin;
element->_physicalMax = _physicalMax;
element->_units = _units;
element->_unitExponent = _unitExponent;
}
if ( ( _owner->registerElement( element, &element->_cookie ) == false )
|| ( _parent && ( _parent->addChildElement(element) == false ) ) )
{
element->release();
element = 0;
}
return element;
}
bool IOHIDElement::createSubElements()
{
IOHIDElement * element;
UInt32 count = getRangeCount();
UInt32 index = 1;
bool ret = true;
while ( index < count)
{
element = newSubElement( index++ );
if ( element == 0 )
{
ret = false;
break;
}
element->release();
}
return ret;
}
void IOHIDElement::free()
{
if ( _childArray )
{
_childArray->release();
_childArray = 0;
}
if ( _queueArray )
{
_queueArray->release();
_queueArray = 0;
}
if ( _arrayItems )
{
_arrayItems->release();
_arrayItems = 0;
}
if (_oldArraySelectors)
{
IOFree (_oldArraySelectors, sizeof(UInt32) * _reportCount);
_oldArraySelectors = 0;
}
if (_colArrayReportHandlers)
{
_colArrayReportHandlers->release();
_colArrayReportHandlers = 0;
}
super::free();
}
bool IOHIDElement::addChildElement( IOHIDElement * child )
{
if ( _childArray == 0 )
{
_childArray = OSArray::withCapacity(4);
}
if ( !_childArray )
return false;
if ((child->_type != kIOHIDElementTypeCollection) && IsArrayElement(child->_flags))
{
if (_colArrayReportHandlers ==0)
{
_colArrayReportHandlers = OSDictionary::withCapacity(1);
}
if (! _colArrayReportHandlers)
return false;
IOHIDElement * arrayReportHandler;
char uniqueID[33];
sprintf(uniqueID, "%d",((child->_reportType << 24) | (child->_reportID << 16) | (child->_reportStartBit)));
uniqueID[32] = 0;
arrayReportHandler = _colArrayReportHandlers->getObject(uniqueID);
if (arrayReportHandler)
{
child->_arrayReportHandler = arrayReportHandler;
}
else
{
arrayReportHandler = arrayHandlerElement(child->_owner, child->_type, child, this);
if ( arrayReportHandler == 0 )
{
return false;
}
_colArrayReportHandlers->setObject(uniqueID, arrayReportHandler);
_childArray->setObject( arrayReportHandler );
arrayReportHandler->release();
}
child->_arrayReportHandler = arrayReportHandler;
child->_reportBits = 1;
child->_reportCount = 1;
child->_logicalMin = child->_physicalMin = 0;
child->_logicalMax = child->_physicalMax = 1;
arrayReportHandler->_arrayItems->setObject(child);
}
_childArray->setObject( child );
child->_parent = this;
return true;
}
IOHIDElement * IOHIDElement::arrayHandlerElement(
IOHIDDevice * owner,
IOHIDElementType type,
IOHIDElement * child,
IOHIDElement * parent)
{
IOHIDElement * element = new IOHIDElement;
if ( (element == 0 ) ||
( element->init( owner, type ) == false ) )
{
if ( element ) element->release();
return 0;
}
element->_arrayReportHandler = element;
element->_parent = parent;
element->_flags = child->_flags;
element->_reportID = child->_reportID;
element->_usagePage = child->_usagePage;
element->_usageMin = 0xffffffff;
element->_usageMax = 0xffffffff;
element->_reportBits = child->_reportBits;
element->_reportCount = child->_reportCount;
element->_reportStartBit = child->_reportStartBit;
element->_logicalMin = child->_logicalMin;
element->_logicalMax = child->_logicalMax;
element->_physicalMin = child->_physicalMin;
element->_physicalMax = child->_physicalMax;
element->_arrayItems = OSArray::withCapacity(child->getRangeCount() ? child->getRangeCount() : 4);
if (element->_arrayItems == NULL)
return false;
element->_oldArraySelectors = (UInt32 *)IOMalloc(sizeof(UInt32) * element->_reportCount);
if (element->_oldArraySelectors == NULL)
return false;
if ( owner->registerElement( element, &element->_cookie ) == false )
{
element->release();
element = 0;
}
return element;
}
bool IOHIDElement::serialize( OSSerialize * s ) const
{
IORegistryEntry * entry;
UInt32 usage;
bool ret = false;
do {
entry = new IORegistryEntry;
if ( entry == 0 ) break;
if ( entry->init() == false ) break;
usage = (_usageMax != _usageMin) ?
_usageMin + _rangeIndex :
_usageMin;
entry->setProperty( kIOHIDElementKey, _childArray );
entry->setProperty( kIOHIDElementCookieKey, (UInt32) _cookie, 32 );
entry->setProperty( kIOHIDElementTypeKey, _type, 32 );
entry->setProperty( kIOHIDElementUsageKey, usage, 32 );
entry->setProperty( kIOHIDElementUsagePageKey, _usagePage, 32 );
if ( _type == kIOHIDElementTypeCollection )
{
ret = true;
break;
}
entry->setProperty( kIOHIDElementValueLocationKey,
(UInt32) _elementValueLocation, 32 );
entry->setProperty( kIOHIDElementHasNullStateKey,
_flags & kHIDDataNullState );
entry->setProperty( kIOHIDElementHasPreferredStateKey,
!(_flags & kHIDDataNoPreferred) );
entry->setProperty( kIOHIDElementIsNonLinearKey,
_flags & kHIDDataNonlinear );
entry->setProperty( kIOHIDElementIsRelativeKey,
_flags & kHIDDataRelative );
entry->setProperty( kIOHIDElementIsWrappingKey,
_flags & kHIDDataWrap );
entry->setProperty( kIOHIDElementIsArrayKey,
IsArrayElement(_flags) );
entry->setProperty( kIOHIDElementMaxKey, _logicalMax, 32 );
entry->setProperty( kIOHIDElementMinKey, _logicalMin, 32 );
entry->setProperty( kIOHIDElementScaledMaxKey, _physicalMax, 32 );
entry->setProperty( kIOHIDElementScaledMinKey, _physicalMin, 32 );
entry->setProperty( kIOHIDElementSizeKey, (_reportBits * _reportCount), 32 );
entry->setProperty( kIOHIDElementReportSizeKey, _reportBits, 32 );
entry->setProperty( kIOHIDElementReportCountKey, _reportCount, 32 );
if ( !IsArrayElement(_flags) )
{
entry->setProperty( kIOHIDElementUnitKey, _units, 32 );
entry->setProperty( kIOHIDElementUnitExponentKey, _unitExponent, 32 );
}
ret = true;
}
while ( false );
if ( entry )
{
if ( ret ) ret = entry->serializeProperties(s);
entry->release();
}
return ret;
}
UInt32 IOHIDElement::getElementValueSize() const
{
UInt32 size = sizeof(IOHIDElementValue);
UInt32 reportWords = (_reportBits * _reportCount) / (sizeof(UInt32) * 8);
reportWords += ((_reportBits * _reportCount) % (sizeof(UInt32) * 8)) ? 1 : 0;
if ( reportWords > 1 )
{
size += ((reportWords - 1) * sizeof(UInt32));
}
return size;
}
#define BIT_MASK(bits) ((1 << (bits)) - 1)
#define UpdateByteOffsetAndShift(bits, offset, shift) \
do { offset = bits >> 3; shift = bits & 0x07; } while (0)
#define UpdateWordOffsetAndShift(bits, offset, shift) \
do { offset = bits >> 5; shift = bits & 0x1f; } while (0)
static void getReportBits( const UInt8 * src,
UInt32 * dst,
UInt32 srcStartBit,
UInt32 bitsToCopy,
bool shouldSignExtend,
bool * valueChanged )
{
UInt32 srcOffset;
UInt32 srcShift;
UInt32 dstShift = 0;
UInt32 dstStartBit = 0;
UInt32 dstOffset = 0;
UInt32 lastDstOffset = 0;
UInt32 word = 0;
UInt8 bitsProcessed;
UInt32 totalBitsProcessed = 0;
while ( bitsToCopy )
{
UInt32 tmp;
UpdateByteOffsetAndShift( srcStartBit, srcOffset, srcShift );
bitsProcessed = min( bitsToCopy,
min( 8 - srcShift, 32 - dstShift ) );
tmp = (src[srcOffset] >> srcShift) & BIT_MASK(bitsProcessed);
word |= ( tmp << dstShift );
dstStartBit += bitsProcessed;
srcStartBit += bitsProcessed;
bitsToCopy -= bitsProcessed;
totalBitsProcessed += bitsProcessed;
UpdateWordOffsetAndShift( dstStartBit, dstOffset, dstShift );
if ( ( dstOffset != lastDstOffset ) || ( bitsToCopy == 0 ) )
{
if ((lastDstOffset == 0) &&
(shouldSignExtend))
{
if ((totalBitsProcessed < 32) &&
(word & (1 << (totalBitsProcessed - 1))))
word |= ~(BIT_MASK(totalBitsProcessed));
}
if ( dst[lastDstOffset] != word )
{
dst[lastDstOffset] = word;
*valueChanged = true;
}
word = 0;
lastDstOffset = dstOffset;
}
}
}
static void setReportBits( const UInt32 * src,
UInt8 * dst,
UInt32 dstStartBit,
UInt32 bitsToCopy)
{
UInt32 dstOffset;
UInt32 dstShift;
UInt32 srcShift = 0;
UInt32 srcStartBit = 0;
UInt32 srcOffset = 0;
UInt8 bitsProcessed;
UInt32 tmp;
while ( bitsToCopy )
{
UpdateByteOffsetAndShift( dstStartBit, dstOffset, dstShift );
bitsProcessed = min( bitsToCopy,
min( 8 - dstShift, 32 - srcShift ) );
tmp = (src[srcOffset] >> srcShift) & BIT_MASK(bitsProcessed);
dst[dstOffset] |= ( tmp << dstShift );
dstStartBit += bitsProcessed;
srcStartBit += bitsProcessed;
bitsToCopy -= bitsProcessed;
UpdateWordOffsetAndShift( srcStartBit, srcOffset, srcShift );
}
}
bool IOHIDElement::processReport( UInt8 reportID,
void * reportData,
UInt32 reportBits,
const AbsoluteTime * timestamp,
IOHIDElement ** next )
{
IOHIDEventQueue * queue;
bool changed = false;
if (IsArrayElement(_flags) && !IsArrayReportHandler(this))
{
*next = _arrayReportHandler;
return false;
}
*next = _nextReportHandler;
do {
if ( _reportID != reportID )
break;
if ( _reportSize && ( reportBits < _reportSize ) )
{
*next = 0;
break;
}
_elementValue->generation++;
getReportBits( (UInt8 *) reportData,
_elementValue->value,
_reportStartBit,
(_reportBits * _reportCount),
(((SInt32)_logicalMin < 0) || ((SInt32)_logicalMax < 0)),
&changed );
if ( changed || (_flags & kHIDDataRelativeBit) || (_elementValue->generation == 1))
{
_elementValue->timestamp = *timestamp;
if (IsArrayElement(_flags) && IsArrayReportHandler(this))
processArrayReport(reportData);
if ( _queueArray )
{
for ( UInt32 i = 0;
(queue = (IOHIDEventQueue *) _queueArray->getObject(i));
i++ )
{
queue->enqueue( (void *) _elementValue,
_elementValue->totalSize );
}
}
}
_elementValue->generation++;
if (_transactionState)
_transactionState = kIOHIDTransactionStateIdle;
}
while ( false );
return changed;
}
bool IOHIDElement::createReport( UInt8 reportID,
void * reportData, UInt32 * reportLength,
IOHIDElement ** next )
{
bool handled = false;
*next = _nextReportHandler;
do {
if ( _reportID != reportID )
break;
if ( _reportSize )
{
*reportLength = _reportSize / 8;
if ( reportData == 0 )
{
*next = 0;
break;
}
bzero( reportData, *reportLength );
}
if (IsArrayElement(_flags) && !IsArrayReportHandler(this))
{
*next = _arrayReportHandler;
break;
}
if ( _transactionState == kIOHIDTransactionStateIdle )
{
if (!IsArrayElement(_flags))
{
setOutOfBoundsValue();
}
else
{
createArrayReport(reportData);
handled = true;
break;
}
}
if ( reportData )
{
setReportBits( _elementValue->value,
(UInt8 *) reportData,
_reportStartBit,
(_reportBits * _reportCount));
handled = true;
_transactionState = kIOHIDTransactionStateIdle;
}
}
while ( false );
return handled;
}
bool IOHIDElement::setMemoryForElementValue( IOVirtualAddress address,
void * location )
{
_elementValue = (IOHIDElementValue *) address;
_elementValueLocation = location;
bzero( _elementValue, getElementValueSize() );
_elementValue->cookie = _cookie;
_elementValue->totalSize = getElementValueSize();
return true;
}
UInt32 IOHIDElement::getRangeCount() const
{
return ( _usageMax - _usageMin + 1 );
}
IOHIDElement *
IOHIDElement::setNextReportHandler( IOHIDElement * element )
{
IOHIDElement * prev = _nextReportHandler;
_nextReportHandler = element;
return prev;
}
bool IOHIDElement::getReportType( IOHIDReportType * reportType ) const
{
if ( _type <= kIOHIDElementTypeInput_ScanCodes )
*reportType = kIOHIDReportTypeInput;
else if ( _type == kIOHIDElementTypeOutput )
*reportType = kIOHIDReportTypeOutput;
else if ( _type == kIOHIDElementTypeFeature )
*reportType = kIOHIDReportTypeFeature;
else
return false;
return true;
}
bool IOHIDElement::addEventQueue( IOHIDEventQueue * queue )
{
if ( _queueArray == 0 )
{
_queueArray = OSArray::withCapacity(4);
}
if ( hasEventQueue(queue) == true )
return false;
return _queueArray ? _queueArray->setObject( queue ) : false;
}
bool IOHIDElement::removeEventQueue( IOHIDEventQueue * queue )
{
OSObject * obj = 0;
for ( UInt32 i = 0;
_queueArray && (obj = _queueArray->getObject(i)); i++ )
{
if ( obj == (OSObject *) queue )
{
_queueArray->removeObject(i);
if ( _queueArray->getCount() == 0 )
{
_queueArray->release();
_queueArray = 0;
}
break;
}
}
return (obj != 0);
}
bool IOHIDElement::hasEventQueue( IOHIDEventQueue * queue )
{
OSObject * obj = 0;
for ( UInt32 i = 0;
_queueArray && (obj = _queueArray->getObject(i)); i++ )
{
if ( obj == (OSObject *) queue )
break;
}
return (obj != 0);
}
UInt32 IOHIDElement::setReportSize( UInt32 numberOfBits )
{
UInt32 oldSize = _reportSize;
_reportSize = numberOfBits;
return oldSize;
}
void IOHIDElement::setOutOfBoundsValue()
{
if ( _elementValue->totalSize == sizeof(IOHIDElementValue) && (_reportCount == 1)) {
if ( _logicalMin > 0 ) {
_elementValue->value[0] = 0;
}
else {
if ( ( BIT_MASK(_reportBits) - _logicalMax ) > 0 )
_elementValue->value[0] = _logicalMax + 1;
else if ( ( -(BIT_MASK(_reportBits)) - _logicalMin ) < 0 )
_elementValue->value[0] = _logicalMin - 1;
}
}
}
void IOHIDElement::createArrayReport(void * reportData)
{
IOHIDElement *element;
bool changed;
UInt32 arraySel;
UInt32 reportIndex = 0;
UInt32 startBit = _reportStartBit;
for (int i=0; _arrayItems && i<_arrayItems->getCount(); i++)
{
element = (IOHIDElement *)(_arrayItems->getObject(i));
if (!element)
continue;
if ( element->_transactionState == kIOHIDTransactionStateIdle )
continue;
if (element->_elementValue->value[0] == 0)
continue;
arraySel = GetArrayItemSel(i);
setReportBits( &arraySel,
(UInt8 *) reportData,
startBit,
_reportBits);
startBit += _reportBits;
reportIndex ++;
element->_transactionState = kIOHIDTransactionStateIdle;
if (reportIndex >= _reportCount)
break;
}
arraySel = 0;
for (i=reportIndex; i<_reportCount; i++)
{
setReportBits( &arraySel,
(UInt8 *) reportData,
startBit,
_reportBits);
startBit += _reportBits;
}
getReportBits( (UInt8 *) reportData, _elementValue->value, _reportStartBit, _reportBits * _reportCount, (((SInt32)_logicalMin < 0) ||
((SInt32)_logicalMax < 0)), &changed );
}
void IOHIDElement::setArrayElementValue(UInt32 index, UInt32 value)
{
IOHIDElement *element;
IOHIDEventQueue *queue;
if ( !_arrayItems || (index > _arrayItems->getCount()))
return;
element = (IOHIDElement *)(_arrayItems->getObject(index));
if (!element)
return;
element->_elementValue->generation ++;
element->_elementValue->value[0] = value;
element->_elementValue->timestamp = _elementValue->timestamp;
element->_elementValue->generation ++;
if ( element->_queueArray )
{
for ( UInt32 i = 0;
(queue = (IOHIDEventQueue *) element->_queueArray->getObject(i));
i++ )
{
queue->enqueue( (void *) element->_elementValue,
element->_elementValue->totalSize );
}
}
}
void IOHIDElement::processArrayReport(void * reportData)
{
UInt32 newArray[_reportCount];
UInt32 arraySel, prevArraySel;
UInt32 iNewArray, iOldArray, startBit;
bool found, changed;
if (_elementValue->generation == 1)
{
for (int i=0; i < _arrayItems->getCount(); i++)
setArrayElementValue(i, 0);
}
bzero(newArray, (_reportCount * sizeof(UInt32)));
startBit = _reportStartBit;
for (iNewArray = 0; iNewArray < _reportCount; iNewArray ++)
{
startBit += _reportBits * iNewArray;
getReportBits( (UInt8 *) reportData, &newArray[iNewArray], startBit, _reportBits, (((SInt32)_logicalMin < 0) ||
((SInt32)_logicalMax < 0)), &changed ); }
for (iOldArray = 0; iOldArray < _reportCount; iOldArray ++)
{
arraySel = _oldArraySelectors[iOldArray];
if ((iOldArray > 0) && (prevArraySel == arraySel))
break;
prevArraySel = arraySel;
found = false;
for (iNewArray = 0; iNewArray < _reportCount; iNewArray ++)
{
if (arraySel == newArray[iNewArray])
{
found = true;
break;
}
}
if (!found)
setArrayElementValue(GetArrayItemIndex(arraySel), 0);
}
for (iNewArray = 0; iNewArray < _reportCount; iNewArray ++)
{
arraySel = newArray[iNewArray];
if ((iNewArray > 0) && (prevArraySel == arraySel))
break;
prevArraySel = arraySel;
found = false;
for (iOldArray = 0; iOldArray < _reportCount; iOldArray ++)
{
if (arraySel == _oldArraySelectors[iOldArray])
{
found = true;
break;
}
}
if (!found)
setArrayElementValue(GetArrayItemIndex(arraySel), 1);
}
for (iOldArray = 0; iOldArray < _reportCount; iOldArray ++)
_oldArraySelectors[iOldArray] = newArray[iOldArray];
}