#include <os/log.h>
#include <DriverKit/IOLib.h>
#include <DriverKit/IOBufferMemoryDescriptor.h>
#include <DriverKit/IOMemoryDescriptor.h>
#include <DriverKit/IOMemoryMap.h>
#include <DriverKit/IODMACommand.h>
#include <DriverKit/IOUserClient.h>
#include <DriverKit/OSAction.h>
#include "CybMemAllocDriver.h"
static constexpr uint64_t kAllocSize = 16 * 1024 * 1024;
static constexpr uint32_t kMaxSegments = 4096;
struct CybAllocInfo {
uint64_t allocSize; uint64_t segmentCount; uint64_t flags; uint64_t reserved; };
struct CybSegmentEntry {
uint64_t physAddr; uint64_t length; };
struct CybMemAllocDriver_IVars {
IOBufferMemoryDescriptor * buffer;
uint64_t allocSize;
uint32_t segmentCount;
CybSegmentEntry segments[kMaxSegments];
};
bool CybMemAllocDriver::init()
{
if (!super::init()) {
return false;
}
ivars = IONewZero(CybMemAllocDriver_IVars, 1);
if (!ivars) {
return false;
}
os_log(OS_LOG_DEFAULT, "CybMemAllocDriver::init()");
return true;
}
kern_return_t CybMemAllocDriver::Start(IOService * provider)
{
kern_return_t ret;
os_log(OS_LOG_DEFAULT, "CybMemAllocDriver::Start() โ allocating %llu bytes contiguous buffer",
kAllocSize);
ret = super::Start(provider);
if (ret != kIOReturnSuccess) {
os_log(OS_LOG_DEFAULT, "CybMemAllocDriver::Start() โ super::Start failed: 0x%x", ret);
return ret;
}
uint64_t options = kIOMemoryDirectionInOut;
ret = IOBufferMemoryDescriptor::Create(
options,
kAllocSize,
16384, &ivars->buffer
);
if (ret != kIOReturnSuccess || !ivars->buffer) {
os_log(OS_LOG_DEFAULT,
"CybMemAllocDriver::Start() โ IOBufferMemoryDescriptor::Create failed: 0x%x", ret);
return ret != kIOReturnSuccess ? ret : kIOReturnNoMemory;
}
ivars->allocSize = kAllocSize;
os_log(OS_LOG_DEFAULT,
"CybMemAllocDriver::Start() โ buffer allocated: %llu bytes", kAllocSize);
IODMACommand * dmaCmd = nullptr;
IODMACommandSpecification spec;
bzero(&spec, sizeof(spec));
spec.options = kIODMACommandSpecificationNoOptions;
spec.maxAddressBits = 64;
ret = IODMACommand::Create(provider, kIODMACommandCreateNoOptions, &spec, &dmaCmd);
if (ret == kIOReturnSuccess && dmaCmd) {
ret = dmaCmd->PrepareForDMA(
kIODMACommandPrepareForDMANoOptions,
ivars->buffer,
0, kAllocSize, nullptr, nullptr, nullptr );
if (ret == kIOReturnSuccess) {
uint64_t offset = 0;
uint32_t idx = 0;
while (offset < kAllocSize && idx < kMaxSegments) {
uint64_t segAddr = 0;
uint64_t segLen = 0;
uint32_t count = 1;
IODMACommandDMASegment seg;
ret = dmaCmd->GetPreparation(nullptr, nullptr, nullptr);
IOAddressSegment addrSeg;
ret = ivars->buffer->GetAddressRange(&addrSeg);
if (ret == kIOReturnSuccess && idx == 0) {
ivars->segments[0].physAddr = addrSeg.address;
ivars->segments[0].length = addrSeg.length;
ivars->segmentCount = 1;
os_log(OS_LOG_DEFAULT,
"CybMemAllocDriver: address range: addr=0x%llx len=%llu",
addrSeg.address, addrSeg.length);
}
break;
}
dmaCmd->CompleteDMA(kIODMACommandCompleteDMANoOptions);
} else {
os_log(OS_LOG_DEFAULT,
"CybMemAllocDriver: PrepareForDMA failed: 0x%x", ret);
ivars->segmentCount = 0;
}
OSSafeReleaseNULL(dmaCmd);
} else {
os_log(OS_LOG_DEFAULT,
"CybMemAllocDriver: IODMACommand::Create failed: 0x%x โ "
"segment info will be unavailable", ret);
ivars->segmentCount = 0;
}
ret = RegisterService();
if (ret != kIOReturnSuccess) {
os_log(OS_LOG_DEFAULT,
"CybMemAllocDriver::Start() โ RegisterService failed: 0x%x", ret);
return ret;
}
os_log(OS_LOG_DEFAULT,
"CybMemAllocDriver::Start() โ ready, %u segment(s) discovered",
ivars->segmentCount);
return kIOReturnSuccess;
}
kern_return_t CybMemAllocDriver::Stop(IOService * provider)
{
os_log(OS_LOG_DEFAULT, "CybMemAllocDriver::Stop()");
return super::Stop(provider);
}
void CybMemAllocDriver::free()
{
os_log(OS_LOG_DEFAULT, "CybMemAllocDriver::free()");
if (ivars) {
OSSafeReleaseNULL(ivars->buffer);
IODelete(ivars, CybMemAllocDriver_IVars, 1);
ivars = nullptr;
}
super::free();
}
kern_return_t CybMemAllocDriver::ExternalMethod(
uint64_t selector,
IOUserClientMethodArguments * arguments,
const IOUserClientMethodDispatch * dispatch,
OSObject * target,
void * reference)
{
switch (selector) {
case kCybMemAllocGetInfo: {
if (!arguments || !arguments->structureOutput ||
arguments->structureOutputSize < sizeof(CybAllocInfo)) {
return kIOReturnBadArgument;
}
CybAllocInfo * info = (CybAllocInfo *)arguments->structureOutput;
info->allocSize = ivars->allocSize;
info->segmentCount = ivars->segmentCount;
info->flags = kIOMemoryDirectionInOut;
info->reserved = 0;
arguments->structureOutputSize = sizeof(CybAllocInfo);
return kIOReturnSuccess;
}
case kCybMemAllocGetSegments: {
if (!arguments || !arguments->structureOutput) {
return kIOReturnBadArgument;
}
uint32_t count = ivars->segmentCount;
uint64_t needed = count * sizeof(CybSegmentEntry);
if (arguments->structureOutputSize < needed) {
return kIOReturnNoSpace;
}
CybSegmentEntry * out = (CybSegmentEntry *)arguments->structureOutput;
for (uint32_t i = 0; i < count; i++) {
out[i] = ivars->segments[i];
}
arguments->structureOutputSize = needed;
return kIOReturnSuccess;
}
default:
return kIOReturnUnsupported;
}
}
kern_return_t CybMemAllocDriver::CopyClientMemoryForType(
uint64_t type,
uint64_t * options,
IOMemoryDescriptor ** memory)
{
if (type != kCybMemAllocMemoryType) {
os_log(OS_LOG_DEFAULT,
"CybMemAllocDriver::CopyClientMemoryForType โ unknown type %llu", type);
return kIOReturnBadArgument;
}
if (!ivars->buffer) {
os_log(OS_LOG_DEFAULT,
"CybMemAllocDriver::CopyClientMemoryForType โ no buffer allocated");
return kIOReturnNotReady;
}
ivars->buffer->retain();
*memory = ivars->buffer;
os_log(OS_LOG_DEFAULT,
"CybMemAllocDriver::CopyClientMemoryForType โ returning buffer (%llu bytes)",
ivars->allocSize);
return kIOReturnSuccess;
}