#include <IOKit/IOLib.h>
#include <IOKit/IOLocks.h>
#include <IOKit/assert.h>
#include <IOKit/IOKitKeys.h>
#include <IOKit/graphics/IOAccelerator.h>
#include <IOKit/graphics/IOGraphicsTypesPrivate.h>
#include <IOKit/IOUserClient.h>
OSDefineMetaClassAndStructors(IOAccelerator, IOService)
namespace {
const uint32_t kUnusedID = 0x80808080; const int kInitTableSize = 32 * sizeof(uint32_t);
inline uint32_t *getID(OSData *data, int index)
{
const uint32_t *uP = static_cast<const uint32_t*>(
data->getBytesNoCopy(index * sizeof(*uP), sizeof(*uP)));
return const_cast<uint32_t*>(uP);
}
bool growData(uint32_t value, uint32_t fill, OSData *data)
{
const int index = data->getLength() / sizeof(value);
bool ret = data->appendByte(fill, data->getCapacityIncrement());
if (ret) {
uint32_t *uP = getID(data, index);
*uP = value;
}
return ret;
}
struct MutexLock {
MutexLock(IOLock *lock) : fLock(lock) { IOLockLock(fLock); }
~MutexLock() { IOLockUnlock(fLock); }
private:
IOLock * const fLock;
};
class IDState {
private:
IOLock *fLock;
OSData *fSpecifiedIDsData;
OSData *fRetainIDsData;
static const uint32_t kMaxID = 64 * 1024; static const uint32_t kMaxRequestedZigZag = 8191; static const uint32_t kAllocatedIDBase = 4096;
static const uint32_t kTaskOwned = (1 << (8*sizeof(kTaskOwned) - 1));
static const uint32_t kNullRef = 0;
struct IDDataDecode {
uint32_t *fUP;
OSData *fIDsData;
uint32_t fArrayInd;
};
inline uint32_t int2zz(int32_t x) { return (x << 1) ^ (x >> 31); }
inline IOReturn reserveRequested(uint32_t taskOwned, IOAccelID id,
IOAccelID *idOutP)
{
const uint32_t oneRef = 1 | taskOwned;
uint32_t zzid = int2zz(id);
MutexLock locked(fLock);
if (zzid > kMaxRequestedZigZag)
return kIOReturnExclusiveAccess;
uint32_t *uP = getID(fSpecifiedIDsData, zzid);
if (uP) {
if (*uP)
return kIOReturnExclusiveAccess;
*uP = oneRef;
*idOutP = id;
return kIOReturnSuccess;
}
if (growData(oneRef, kNullRef, fSpecifiedIDsData)) {
*idOutP = id;
return kIOReturnSuccess;
}
else
return kIOReturnNoMemory;
}
inline IOReturn allocID(uint32_t taskOwned, IOAccelID *idOutP)
{
const uint32_t oneRef = 1 | taskOwned;
MutexLock locked(fLock);
int i;
for (i = 0; uint32_t *uP = getID(fRetainIDsData, i); ++i)
if (!*uP) {
*uP = oneRef;
*idOutP = i + kAllocatedIDBase;
return kIOReturnSuccess;
}
if (i >= kMaxID) return kIOReturnNoResources;
bool res = growData(oneRef, kNullRef, fRetainIDsData);
assert(*getID(fRetainIDsData, i) == oneRef);
if (res) {
*idOutP = i + kAllocatedIDBase;
return kIOReturnSuccess;
}
else
return kIOReturnNoMemory;
}
IDDataDecode locateID(IOAccelID id)
{
IDDataDecode ret = {
.fIDsData = fRetainIDsData,
.fArrayInd = id - kAllocatedIDBase,
};
if (id < kAllocatedIDBase) {
ret.fIDsData = fSpecifiedIDsData;
ret.fArrayInd = int2zz(id);
}
ret.fUP = getID(ret.fIDsData, ret.fArrayInd);
return ret;
}
public:
IDState() : fLock(IOLockAlloc()),
fSpecifiedIDsData(OSData::withCapacity(kInitTableSize)),
fRetainIDsData(OSData::withCapacity(kInitTableSize))
{
if (!fLock || !fSpecifiedIDsData || !fRetainIDsData) return;
if (!fSpecifiedIDsData->appendByte(kNullRef, kInitTableSize)) return;
if (!fRetainIDsData->appendByte(kNullRef, kInitTableSize)) return;
}
bool isValid() { return getID(fRetainIDsData, 0); }
~IDState()
{ OSSafeReleaseNULL(fSpecifiedIDsData);
OSSafeReleaseNULL(fRetainIDsData);
if (fLock) {
IOLockFree(fLock);
fLock = NULL;
}
}
IOLock *lock() const { return fLock; }
IOReturn createID(const bool taskOwned, const IOOptionBits options,
const IOAccelID requestedID, IOAccelID *idOutP)
{
const uint32_t taskOwnedMask = (taskOwned) ? kTaskOwned : 0;
IOReturn ret;
if (kIOAccelSpecificID & options)
ret = reserveRequested(taskOwnedMask, requestedID, idOutP);
else
ret = allocID(taskOwnedMask, idOutP);
return ret;
}
IOReturn validID(int32_t id)
{
const IDDataDecode d = locateID(id);
if (d.fUP && *d.fUP & ~kTaskOwned) return kIOReturnSuccess;
else
return kIOReturnBadMessageID;
}
void retainID(int32_t id)
{
assert(validID(id));
const IDDataDecode d = locateID(id);
const auto value = *d.fUP;
if ((value + 1) & ~kTaskOwned)
*d.fUP = value + 1;
else
*d.fUP = (value & kTaskOwned) | (-1U >> 1); }
void releaseID(bool taskOwned, int32_t id)
{
assert(validID(id));
const IDDataDecode d = locateID(id);
const auto idOwned = *d.fUP & kTaskOwned;
const auto value = *d.fUP & ~kTaskOwned;
if (!taskOwned && idOwned && value == 1)
panic("IOAccelerator::releaseID still task owned");
assert(value);
if (value) {
*d.fUP = (value - 1) | (taskOwned ? 0 : idOwned);
}
}
};
IDState sIDState; };
OSCompileAssert(sizeof(kUnusedID) == sizeof(IOAccelID));
class IOAccelerationUserClient : public IOUserClient
{
OSDeclareDefaultStructors(IOAccelerationUserClient);
using super = IOUserClient;
private:
OSData *fIDListData;
IOReturn extCreate(IOOptionBits options,
IOAccelID requestedID, IOAccelID *idOutP);
IOReturn extDestroy(IOOptionBits options, IOAccelID id);
public:
virtual void free();
virtual bool start(IOService *provider);
virtual void stop(IOService *provider);
virtual bool initWithTask(task_t, void*, uint32_t, OSDictionary*);
virtual IOReturn clientClose();
virtual IOExternalMethod *
getTargetAndMethodForIndex(IOService **targetP, uint32_t index);
};
OSDefineMetaClassAndStructors(IOAccelerationUserClient, IOUserClient);
bool IOAccelerationUserClient::
initWithTask(task_t owningTask, void *securityID, UInt32 type,
OSDictionary *properties)
{
if (properties)
properties->setObject(
kIOUserClientCrossEndianCompatibleKey, kOSBooleanTrue);
bool ret = super::initWithTask(owningTask, securityID, type, properties);
if (ret)
ret = sIDState.isValid();
return ret;
}
bool IOAccelerationUserClient::start(IOService *provider)
{
bool ret = super::start(provider);
if (ret) {
fIDListData = OSData::withCapacity(kInitTableSize);
if (fIDListData)
ret = fIDListData->appendByte(kUnusedID,
fIDListData->getCapacity());
else
ret = false;
}
return ret;
}
void IOAccelerationUserClient::free()
{
OSSafeReleaseNULL(fIDListData);
super::free();
}
IOReturn IOAccelerationUserClient::clientClose()
{
if (!isInactive())
terminate();
return kIOReturnSuccess;
}
void IOAccelerationUserClient::stop(IOService *provider)
{
MutexLock locked(sIDState.lock());
for (int i = 0; uint32_t *uP = getID(fIDListData, i); ++i)
if (*uP != kUnusedID) {
sIDState.releaseID( true, *uP);
*uP = kUnusedID;
}
super::stop(provider);
}
IOExternalMethod *IOAccelerationUserClient::
getTargetAndMethodForIndex(IOService **targetP, uint32_t index)
{
static const IOExternalMethod methodTemplate[] =
{
{ NULL, (IOMethod) &IOAccelerationUserClient::extCreate,
kIOUCScalarIScalarO, 2, 1 },
{ NULL, (IOMethod) &IOAccelerationUserClient::extDestroy,
kIOUCScalarIScalarO, 2, 0 },
};
if (index >= (sizeof(methodTemplate) / sizeof(methodTemplate[0])))
return NULL;
*targetP = this;
return const_cast<IOExternalMethod *>(&methodTemplate[index]);
}
namespace {
IOReturn trackID(const IOAccelID id, OSData *idsData)
{
for (int i = 0; uint32_t *uP = getID(idsData, i); ++i)
if (*uP == kUnusedID) {
*uP = id;
return kIOReturnSuccess;
}
if (growData(id, kUnusedID, idsData))
return kIOReturnSuccess;
else
return kIOReturnNoMemory;
}
};
IOReturn IOAccelerationUserClient::
extCreate(IOOptionBits options, IOAccelID requestedID, IOAccelID *idOutP)
{
IOReturn ret = sIDState.createID( true,
options, requestedID, idOutP);
if (kIOReturnSuccess == ret) {
MutexLock locked(sIDState.lock());
ret = trackID(*idOutP, fIDListData);
if (ret)
sIDState.releaseID( true, *idOutP);
}
return ret;
}
IOReturn IOAccelerationUserClient::
extDestroy(IOOptionBits , const IOAccelID id)
{
MutexLock locked(sIDState.lock());
IOReturn ret = sIDState.validID(id);
if (ret) return kIOReturnBadMessageID;
for (int i = 0; uint32_t *uP = getID(fIDListData, i); ++i)
if (id == *uP) {
sIDState.releaseID( true, id);
*uP = kUnusedID;
return kIOReturnSuccess;
}
return kIOReturnBadMessageID;
}
IOReturn
IOAccelerator::createAccelID(IOOptionBits options, IOAccelID *idOutP)
{
IOReturn ret = sIDState.createID( false,
options, *idOutP, idOutP);
return ret;
}
IOReturn
IOAccelerator::retainAccelID(IOOptionBits , IOAccelID id)
{
MutexLock locked(sIDState.lock());
IOReturn ret = sIDState.validID(id);
if (!ret)
sIDState.retainID(id);
return ret;
}
IOReturn
IOAccelerator::releaseAccelID(IOOptionBits , IOAccelID id)
{
MutexLock locked(sIDState.lock());
IOReturn ret = sIDState.validID(id);
if (!ret)
sIDState.releaseID( false, id);
return ret;
}