Source to iokit/Kernel/IOMemoryDescriptor.cpp
/*
* 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 <IOKit/assert.h>
#include <IOKit/system.h>
#include <IOKit/IOLib.h>
#include <IOKit/IOMemoryDescriptor.h>
#include <IOKit/IOKitDebug.h>
#include <libkern/c++/OSContainers.h>
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
OSDefineMetaClass( IOMemoryDescriptor, OSObject )
OSDefineAbstractStructors( IOMemoryDescriptor, OSObject )
#define super IOMemoryDescriptor
OSDefineMetaClassAndStructors(IOGeneralMemoryDescriptor, IOMemoryDescriptor)
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*
* withAddress:
*
* Create a new IOMemoryDescriptor. The buffer is a virtual address
* relative to the specified task. If no task is supplied, the kernel
* task is implied.
*/
IOMemoryDescriptor *
IOMemoryDescriptor::withAddress(void * address,
IOByteCount withLength,
IODirection withDirection)
{
IOGeneralMemoryDescriptor * that = new IOGeneralMemoryDescriptor;
if (that)
{
if (that->initWithAddress(address, withLength, withDirection))
return that;
that->release();
}
return 0;
}
IOMemoryDescriptor *
IOMemoryDescriptor::withAddress(vm_address_t address,
IOByteCount withLength,
IODirection withDirection,
task_t withTask)
{
IOGeneralMemoryDescriptor * that = new IOGeneralMemoryDescriptor;
if (that)
{
if (that->initWithAddress(address, withLength, withDirection, withTask))
return that;
that->release();
}
return 0;
}
IOMemoryDescriptor *
IOMemoryDescriptor::withPhysicalAddress(
IOPhysicalAddress address,
IOByteCount withLength,
IODirection withDirection )
{
return( IOMemoryDescriptor::withAddress( address, withLength,
withDirection, (task_t) 0 ));
}
/*
* withRanges:
*
* Create a new IOMemoryDescriptor. The buffer is made up of several
* virtual address ranges, from a given task.
*
* Passing the ranges as a reference will avoid an extra allocation.
*/
IOMemoryDescriptor *
IOMemoryDescriptor::withRanges( IOVirtualRange * ranges,
UInt32 withCount,
IODirection withDirection,
task_t withTask,
bool asReference = false)
{
IOGeneralMemoryDescriptor * that = new IOGeneralMemoryDescriptor;
if (that)
{
if (that->initWithRanges(ranges, withCount, withDirection, withTask, asReference))
return that;
that->release();
}
return 0;
}
IOMemoryDescriptor *
IOMemoryDescriptor::withPhysicalRanges( IOPhysicalRange * ranges,
UInt32 withCount,
IODirection withDirection,
bool asReference = false)
{
IOGeneralMemoryDescriptor * that = new IOGeneralMemoryDescriptor;
if (that)
{
if (that->initWithPhysicalRanges(ranges, withCount, withDirection, asReference))
return that;
that->release();
}
return 0;
}
IOMemoryDescriptor *
IOMemoryDescriptor::withSubRange(IOMemoryDescriptor * of,
IOByteCount offset,
IOByteCount length,
IODirection withDirection)
{
IOSubMemoryDescriptor * that = new IOSubMemoryDescriptor;
if (that && !that->initSubRange(of, offset, length, withDirection)) {
that->release();
that = 0;
}
return that;
}
/*
* initWithAddress:
*
* Initialize an IOMemoryDescriptor. The buffer is a virtual address
* relative to the specified task. If no task is supplied, the kernel
* task is implied.
*
* An IOMemoryDescriptor can be re-used by calling initWithAddress or
* initWithRanges again on an existing instance -- note this behavior
* is not commonly supported in other I/O Kit classes, although it is
* supported here.
*/
bool
IOGeneralMemoryDescriptor::initWithAddress(void * address,
IOByteCount withLength,
IODirection withDirection)
{
_singleRange.v.address = (vm_address_t) address;
_singleRange.v.length = withLength;
return initWithRanges(&_singleRange.v, 1, withDirection, kernel_task, true);
}
bool
IOGeneralMemoryDescriptor::initWithAddress(vm_address_t address,
IOByteCount withLength,
IODirection withDirection,
task_t withTask)
{
_singleRange.v.address = address;
_singleRange.v.length = withLength;
return initWithRanges(&_singleRange.v, 1, withDirection, withTask, true);
}
bool
IOGeneralMemoryDescriptor::initWithPhysicalAddress(
IOPhysicalAddress address,
IOByteCount withLength,
IODirection withDirection )
{
_singleRange.p.address = address;
_singleRange.p.length = withLength;
return initWithPhysicalRanges( &_singleRange.p, 1, withDirection, true);
}
/*
* initWithRanges:
*
* Initialize an IOMemoryDescriptor. The buffer is made up of several
* virtual address ranges, from a given task
*
* Passing the ranges as a reference will avoid an extra allocation.
*
* An IOMemoryDescriptor can be re-used by calling initWithAddress or
* initWithRanges again on an existing instance -- note this behavior
* is not commonly supported in other I/O Kit classes, although it is
* supported here.
*/
bool
IOGeneralMemoryDescriptor::initWithRanges(
IOVirtualRange * ranges,
UInt32 withCount,
IODirection withDirection,
task_t withTask,
bool asReference = false)
{
assert(ranges);
assert(withCount);
/*
* We can check the _initialized instance variable before having ever set
* it to an initial value because I/O Kit guarantees that all our instance
* variables are zeroed on an object's allocation.
*/
if (_initialized == false)
{
if (super::init() == false) return false;
_initialized = true;
}
else
{
/*
* An existing memory descriptor is being retargeted to point to
* somewhere else. Clean up our present state.
*/
assert(_wireCount == 0);
while (_wireCount)
complete();
if (_kernPtr)
unmapFromKernel();
if (_ranges.v && _rangesIsAllocated)
IODelete(_ranges.v, IOVirtualRange, _rangesCount);
}
/*
* Initialize the memory descriptor.
*/
_ranges.v = 0;
_rangesCount = withCount;
_rangesIsAllocated = asReference ? false : true;
_direction = withDirection;
_length = 0;
_task = withTask;
_position = 0;
_positionAtIndex = 0;
_positionAtOffset = 0;
_kernPtr = 0;
_cachedPhysicalAddress = 0;
_cachedVirtualAddress = 0;
if (asReference)
_ranges.v = ranges;
else
{
_ranges.v = IONew(IOVirtualRange, withCount);
if (_ranges.v == 0) return false;
bcopy(/* from */ ranges, _ranges.v, withCount * sizeof(IOVirtualRange));
}
for (unsigned index = 0; index < _rangesCount; index++)
{
_length += _ranges.v[index].length;
}
return true;
}
bool
IOGeneralMemoryDescriptor::initWithPhysicalRanges( IOPhysicalRange * ranges,
UInt32 withCount,
IODirection withDirection,
bool asReference = false)
{
#warning assuming virtual, physical addresses same size
return( initWithRanges( (IOVirtualRange *) ranges,
withCount, withDirection, (task_t) 0, asReference ));
}
/*
* free
*
* Free resources.
*/
void IOGeneralMemoryDescriptor::free()
{
while (_wireCount)
complete();
if (_kernPtr)
unmapFromKernel();
if (_ranges.v && _rangesIsAllocated)
IODelete(_ranges.v, IOVirtualRange, _rangesCount);
super::free();
}
void IOGeneralMemoryDescriptor::unmapFromKernel()
{
kern_return_t krtn;
vm_offset_t off;
// Pull the shared pages out of the task map
// Do we need to unwire it first?
for ( off = 0; off < _kernSize; off += page_size )
{
pmap_change_wiring(
kernel_pmap,
_kernPtrAligned + off,
FALSE);
pmap_remove(
kernel_pmap,
_kernPtrAligned + off,
_kernPtrAligned + off + page_size);
}
// Free the former shmem area in the task
krtn = vm_deallocate(kernel_map,
_kernPtrAligned,
_kernSize );
assert(krtn == KERN_SUCCESS);
_kernPtr = 0;
}
void IOGeneralMemoryDescriptor::mapIntoKernel(unsigned rangeIndex)
{
kern_return_t krtn;
vm_offset_t off;
if (_kernPtr)
{
if (_kernPtrAtIndex == rangeIndex) return;
unmapFromKernel();
assert(_kernPtr == 0);
}
vm_offset_t srcAlign = trunc_page(_ranges.v[rangeIndex].address);
_kernSize = trunc_page(_ranges.v[rangeIndex].address +
_ranges.v[rangeIndex].length +
page_size - 1) - srcAlign;
/* Find some memory of the same size in kernel task. We use vm_allocate()
to do this. vm_allocate inserts the found memory object in the
target task's map as a side effect. */
krtn = vm_allocate( kernel_map,
&_kernPtrAligned,
_kernSize,
TRUE ); // Find first fit
assert(krtn == KERN_SUCCESS);
if(krtn) return;
/* For each page in the area allocated from the kernel map,
find the physical address of the page.
Enter the page in the target task's pmap, at the
appropriate target task virtual address. */
for ( off = 0; off < _kernSize; off += page_size )
{
vm_offset_t kern_phys_addr, phys_addr;
if( _task)
phys_addr = pmap_extract( get_task_pmap(_task), srcAlign + off );
else
phys_addr = srcAlign + off;
assert(phys_addr);
if(phys_addr == 0) return;
// Check original state.
kern_phys_addr = pmap_extract( kernel_pmap, _kernPtrAligned + off );
// Set virtual page to point to the right physical one
pmap_enter(
kernel_pmap,
_kernPtrAligned + off,
phys_addr,
VM_PROT_READ|VM_PROT_WRITE,
TRUE);
}
_kernPtr = _kernPtrAligned + (_ranges.v[rangeIndex].address - srcAlign);
_kernPtrAtIndex = rangeIndex;
}
/*
* getDirection:
*
* Get the direction of the transfer.
*/
IODirection IOMemoryDescriptor::getDirection() const
{
return _direction;
}
/*
* getLength:
*
* Get the length of the transfer (over all ranges).
*/
IOByteCount IOMemoryDescriptor::getLength() const
{
return _length;
}
void IOMemoryDescriptor::setTag(
IOOptionBits tag )
{
_tag = tag;
}
IOOptionBits IOMemoryDescriptor::getTag( void )
{
return( _tag);
}
/*
* setPosition
*
* Set the logical start position inside the client buffer.
*
* It is convention that the position reflect the actual byte count that
* is successfully transferred into or out of the buffer, before the I/O
* request is "completed" (ie. sent back to its originator).
*/
void IOGeneralMemoryDescriptor::setPosition(IOByteCount position)
{
assert(position <= _length);
if (position >= _length)
{
_position = _length;
_positionAtIndex = _rangesCount; /* careful: out-of-bounds */
_positionAtOffset = 0;
return;
}
if (position < _position)
{
_positionAtOffset = position;
_positionAtIndex = 0;
}
else
{
_positionAtOffset += (position - _position);
}
_position = position;
while (_positionAtOffset >= _ranges.v[_positionAtIndex].length)
{
_positionAtOffset -= _ranges.v[_positionAtIndex].length;
_positionAtIndex++;
}
}
/*
* readBytes:
*
* Copy data from the memory descriptor's buffer into the specified buffer,
* relative to the current position. The memory descriptor's position is
* advanced based on the number of bytes copied.
*/
IOByteCount IOGeneralMemoryDescriptor::readBytes(IOByteCount offset,
void * bytes, IOByteCount withLength)
{
IOByteCount bytesLeft;
void * segment;
IOByteCount segmentLength;
if( offset != _position)
setPosition( offset );
withLength = min(withLength, _length - _position);
bytesLeft = withLength;
#if 0
while (bytesLeft && (_position < _length))
{
/* Compute the relative length to the end of this virtual segment. */
segmentLength = min(_ranges.v[_positionAtIndex].length - _positionAtOffset, bytesLeft);
/* Compute the relative address of this virtual segment. */
segment = (void *)(_ranges.v[_positionAtIndex].address + _positionAtOffset);
if (KERN_SUCCESS != vm_map_read_user(get_task_map(_task),
/* from */ (vm_offset_t) segment, /* to */ (vm_offset_t) bytes,
/* size */ segmentLength))
{
assert( false );
bytesLeft = withLength;
break;
}
bytesLeft -= segmentLength;
offset += segmentLength;
setPosition(offset);
}
#else
while (bytesLeft && (segment = getVirtualSegment(offset, &segmentLength)))
{
segmentLength = min(segmentLength, bytesLeft);
bcopy(/* from */ segment, /* to */ bytes, /* size */ segmentLength);
bytesLeft -= segmentLength;
offset += segmentLength;
}
#endif
return withLength - bytesLeft;
}
/*
* writeBytes:
*
* Copy data to the memory descriptor's buffer from the specified buffer,
* relative to the current position. The memory descriptor's position is
* advanced based on the number of bytes copied.
*/
IOByteCount IOGeneralMemoryDescriptor::writeBytes(IOByteCount offset,
const void* bytes,IOByteCount withLength)
{
IOByteCount bytesLeft;
void * segment;
IOByteCount segmentLength;
if( offset != _position)
setPosition( offset );
withLength = min(withLength, _length - _position);
bytesLeft = withLength;
#if 0
while (bytesLeft && (_position < _length))
{
assert(_position <= _length);
/* Compute the relative length to the end of this virtual segment. */
segmentLength = min(_ranges.v[_positionAtIndex].length - _positionAtOffset, bytesLeft);
/* Compute the relative address of this virtual segment. */
segment = (void *)(_ranges.v[_positionAtIndex].address + _positionAtOffset);
if (KERN_SUCCESS != vm_map_write_user(get_task_map(_task),
/* from */ (vm_offset_t) bytes,
/* to */ (vm_offset_t) segment,
/* size */ segmentLength))
{
assert( false );
bytesLeft = withLength;
break;
}
bytesLeft -= segmentLength;
offset += segmentLength;
setPosition(offset);
}
#else
while (bytesLeft && (segment = getVirtualSegment(offset, &segmentLength)))
{
segmentLength = min(segmentLength, bytesLeft);
bcopy(/* from */ bytes, /* to */ segment, /* size */ segmentLength);
bytesLeft -= segmentLength;
offset += segmentLength;
}
#endif
return withLength - bytesLeft;
}
/*
* getPhysicalSegment:
*
* Get the physical address of the buffer, relative to the current position.
* If the current position is at the end of the buffer, a zero is returned.
*/
IOPhysicalAddress
IOGeneralMemoryDescriptor::getPhysicalSegment(IOByteCount offset,
IOByteCount * lengthOfSegment)
{
vm_address_t virtualAddress;
IOByteCount virtualLength;
pmap_t virtualPMap;
IOPhysicalAddress physicalAddress;
IOPhysicalLength physicalLength;
if ((0 == _task) && (1 == _rangesCount))
{
assert(offset <= _length);
if (offset >= _length)
{
physicalAddress = 0;
physicalLength = 0;
}
else
{
physicalLength = _length - offset;
physicalAddress = offset + _ranges.v[0].address;
}
if (lengthOfSegment)
*lengthOfSegment = physicalLength;
return physicalAddress;
}
if( offset != _position)
setPosition( offset );
assert(_position <= _length);
/* Fail gracefully if the position is at (or past) the end-of-buffer. */
if (_position >= _length)
{
*lengthOfSegment = 0;
return 0;
}
/* Prepare to compute the largest contiguous physical length possible. */
virtualAddress = _ranges.v[_positionAtIndex].address + _positionAtOffset;
virtualLength = _ranges.v[_positionAtIndex].length - _positionAtOffset;
if( _task)
virtualPMap = get_task_pmap(_task);
else
virtualPMap = 0;
physicalAddress = (virtualAddress == _cachedVirtualAddress) ?
_cachedPhysicalAddress : /* optimization */
virtualPMap ?
pmap_extract(virtualPMap, virtualAddress) :
virtualAddress;
physicalLength = trunc_page(physicalAddress) + page_size - physicalAddress;
if (physicalAddress == 0) /* memory must be wired in order to proceed */
{
assert(physicalAddress);
*lengthOfSegment = 0;
return 0;
}
/* Compute the largest contiguous physical length possible, within range. */
vm_address_t virtualPage = trunc_page(virtualAddress);
IOPhysicalAddress physicalPage = trunc_page(physicalAddress);
while (physicalLength < virtualLength)
{
physicalPage += page_size;
virtualPage += page_size;
_cachedVirtualAddress = virtualPage;
_cachedPhysicalAddress = virtualPMap ?
pmap_extract(virtualPMap, virtualPage) :
virtualPage;
if (_cachedPhysicalAddress != physicalPage) break;
physicalLength += page_size;
}
/* Clip contiguous physical length at the end of this range. */
if (physicalLength > virtualLength)
physicalLength = virtualLength;
if( lengthOfSegment)
*lengthOfSegment = physicalLength;
return physicalAddress;
}
/*
* getVirtualSegment:
*
* Get the virtual address of the buffer, relative to the current position.
* If the memory wasn't mapped into the caller's address space, it will be
* mapped in now. If the current position is at the end of the buffer, a
* null is returned.
*/
void * IOGeneralMemoryDescriptor::getVirtualSegment(IOByteCount offset,
IOByteCount * lengthOfSegment)
{
if( offset != _position)
setPosition( offset );
assert(_position <= _length);
/* Fail gracefully if the position is at (or past) the end-of-buffer. */
if (_position >= _length)
{
*lengthOfSegment = 0;
return 0;
}
/* Compute the relative length to the end of this virtual segment. */
*lengthOfSegment = _ranges.v[_positionAtIndex].length - _positionAtOffset;
/* Compute the relative address of this virtual segment. */
if (_task == kernel_task)
return (void *)(_ranges.v[_positionAtIndex].address + _positionAtOffset);
else
{
mapIntoKernel(_positionAtIndex);
return (void *)(_kernPtr + _positionAtOffset);
}
}
/*
* prepare
*
* Prepare the memory for an I/O transfer. This involves paging in
* the memory, if necessary, and wiring it down for the duration of
* the transfer. The complete() method completes the processing of
* the memory after the I/O transfer finishes. This method needn't
* called for non-pageable memory.
*/
IOReturn IOGeneralMemoryDescriptor::prepare(
IODirection forDirection = kIODirectionNone)
{
IOReturn rc = kIOReturnSuccess;
_wireCount++;
if(_task && (_task != kernel_task) && (_wireCount == 1)) {
UInt rangeIndex;
kern_return_t rc;
vm_prot_t access = VM_PROT_DEFAULT; // Could be cleverer using _direction
//
// Check user read/write access to the data buffer.
//
for (rangeIndex = 0; rangeIndex < _rangesCount; rangeIndex++)
{
vm_offset_t checkBase = trunc_page(_ranges.v[rangeIndex].address);
vm_size_t checkSize = round_page(_ranges.v[rangeIndex].length );
while (checkSize)
{
vm_region_basic_info_data_t regionInfo;
mach_msg_type_number_t regionInfoSize = sizeof(regionInfo);
vm_size_t regionSize;
if ( (vm_region(
/* map */ get_task_map(_task),
/* address */ &checkBase,
/* size */ ®ionSize,
/* flavor */ VM_REGION_BASIC_INFO,
/* info */ (vm_region_info_t) ®ionInfo,
/* info size */ ®ionInfoSize,
/* object name */ 0 ) != KERN_SUCCESS ) ||
( (_direction & kIODirectionIn ) &&
!(regionInfo.protection & VM_PROT_WRITE) ) ||
( (_direction & kIODirectionOut) &&
!(regionInfo.protection & VM_PROT_READ ) ) )
{
return kIOReturnVMError;
}
assert((regionSize & PAGE_MASK) == 0);
regionSize = min(regionSize, checkSize);
checkSize -= regionSize;
checkBase += regionSize;
} // (for each vm region)
} // (for each io range)
for(rangeIndex = 0; rangeIndex < _rangesCount; rangeIndex++) {
vm_offset_t srcAlign = trunc_page(_ranges.v[rangeIndex].address);
IOByteCount srcAlignEnd = trunc_page(_ranges.v[rangeIndex].address +
_ranges.v[rangeIndex].length +
page_size - 1);
rc = vm_map_wire(get_task_map(_task), srcAlign,
srcAlignEnd, access, FALSE);
if(rc != KERN_SUCCESS) {
UInt doneIndex;
IOLog("IOMemoryDescriptor::prepare: vm_map_wire failed: %d\n", rc);
for(doneIndex = 0; doneIndex < rangeIndex; doneIndex++) {
vm_offset_t srcAlign = trunc_page(_ranges.v[doneIndex].address);
IOByteCount srcAlignEnd = trunc_page(_ranges.v[doneIndex].address +
_ranges.v[doneIndex].length +
page_size - 1);
vm_map_unwire(get_task_map(_task), srcAlign,
srcAlignEnd, FALSE);
}
_wireCount = 0; // Back to unwired state now.
}
}
}
return rc;
}
/*
* complete
*
* Complete processing of the memory after an I/O transfer finishes.
* This method should not be called unless a prepare was previously
* issued; the prepare() and complete() must occur in pairs, before
* before and after an I/O transfer involving pageable memory.
*/
IOReturn IOGeneralMemoryDescriptor::complete(
IODirection forDirection = kIODirectionNone)
{
assert(_wireCount);
_wireCount--;
if(_task && (_task != kernel_task) && (_wireCount == 0)) {
UInt rangeIndex;
kern_return_t rc;
for(rangeIndex = 0; rangeIndex < _rangesCount; rangeIndex++) {
vm_offset_t srcAlign = trunc_page(_ranges.v[rangeIndex].address);
IOByteCount srcAlignEnd = trunc_page(_ranges.v[rangeIndex].address +
_ranges.v[rangeIndex].length +
page_size - 1);
rc = vm_map_unwire(get_task_map(_task), srcAlign,
srcAlignEnd, FALSE);
if(rc != KERN_SUCCESS)
IOLog("IOMemoryDescriptor::complete: vm_map_unwire failed: %d\n", rc);
}
}
return kIOReturnSuccess;
}
IOReturn IOGeneralMemoryDescriptor::doMap(
task_t addressTask,
IOVirtualAddress * atAddress,
IOOptionBits options,
IOByteCount sourceOffset = 0,
IOByteCount length = 0 )
{
// could be much better
if( (addressTask == _task) && (options & kIOMapAnywhere)
&& (1 == _rangesCount) && (0 == sourceOffset)
&& (length <= _ranges.v[0].length) ) {
*atAddress = _ranges.v[0].address;
return( kIOReturnSuccess );
}
return( super::doMap( addressTask, atAddress,
options, sourceOffset, length ));
}
IOReturn IOGeneralMemoryDescriptor::doUnmap(
task_t addressTask,
IOVirtualAddress logical,
IOByteCount length )
{
// could be much better
if( (addressTask == _task) && (1 == _rangesCount)
&& (logical == _ranges.v[0].address)
&& (length <= _ranges.v[0].length) )
return( kIOReturnSuccess );
return( super::doUnmap( addressTask, logical, length ));
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
extern "C" {
// osfmk/device/iokit_rpc.c
extern kern_return_t IOMapPages(vm_map_t map, vm_offset_t va, vm_offset_t pa,
vm_size_t length, unsigned int mapFlags);
};
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
static IOLock * gIOMemoryLock;
#define LOCK IOTakeLock( gIOMemoryLock)
#define UNLOCK IOUnlock( gIOMemoryLock)
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
OSDefineMetaClass( IOMemoryMap, OSObject )
OSDefineAbstractStructors( IOMemoryMap, OSObject )
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
class _IOMemoryMap : public IOMemoryMap
{
OSDeclareDefaultStructors(_IOMemoryMap)
IOMemoryDescriptor * memory;
IOMemoryMap * superMap;
IOByteCount offset;
IOByteCount length;
IOVirtualAddress logical;
task_t addressTask;
IOOptionBits options;
public:
virtual void free();
// IOMemoryMap methods
virtual IOVirtualAddress getVirtualAddress();
virtual IOByteCount getLength();
virtual task_t getAddressTask();
virtual IOMemoryDescriptor * getMemoryDescriptor();
virtual IOOptionBits getMapOptions();
virtual IOReturn unmap();
virtual IOPhysicalAddress getPhysicalSegment(IOByteCount offset,
IOByteCount * length);
// for IOMemoryDescriptor use
_IOMemoryMap * isCompatible(
IOMemoryDescriptor * owner,
task_t task,
IOVirtualAddress toAddress,
IOOptionBits options,
IOByteCount offset,
IOByteCount length );
bool _IOMemoryMap::init(
IOMemoryDescriptor * memory,
IOMemoryMap * superMap,
IOByteCount offset,
IOByteCount length );
bool _IOMemoryMap::init(
IOMemoryDescriptor * memory,
task_t intoTask,
IOVirtualAddress toAddress,
IOOptionBits options,
IOByteCount offset,
IOByteCount length );
};
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#undef super
#define super IOMemoryMap
OSDefineMetaClassAndStructors(_IOMemoryMap, IOMemoryMap)
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
bool _IOMemoryMap::init(
IOMemoryDescriptor * _memory,
IOMemoryMap * _superMap,
IOByteCount _offset,
IOByteCount _length )
{
if( !super::init())
return( false);
if( (_offset + _length) > _superMap->getLength())
return( false);
_memory->retain();
memory = _memory;
_superMap->retain();
superMap = _superMap;
offset = _offset;
if( _length)
length = _length;
else
length = _memory->getLength();
options = superMap->getMapOptions();
addressTask = superMap->getAddressTask();
logical = superMap->getVirtualAddress() + offset;
return( true );
}
bool _IOMemoryMap::init(
IOMemoryDescriptor * _memory,
task_t intoTask,
IOVirtualAddress toAddress,
IOOptionBits _options,
IOByteCount _offset,
IOByteCount _length )
{
bool ok;
if( (!_memory) || !super::init())
return( false);
if( (_offset + _length) > _memory->getLength())
return( false);
_memory->retain();
memory = _memory;
offset = _offset;
if( _length)
length = _length;
else
length = _memory->getLength();
addressTask = intoTask;
logical = toAddress;
options = _options;
if( options & kIOMapStatic)
ok = true;
else
ok = (kIOReturnSuccess == memory->doMap( addressTask, &logical,
options, offset, length ));
if( !ok)
logical = 0;
return( ok );
}
// documentation lies
#define VM_ALLOCATE_ANYWHERE TRUE
#define VM_ALLOCATE_RESERVED FALSE
IOReturn IOMemoryDescriptor::doMap(
task_t addressTask,
IOVirtualAddress * atAddress,
IOOptionBits options,
IOByteCount sourceOffset = 0,
IOByteCount length = 0 )
{
IOReturn err = kIOReturnSuccess;
vm_size_t ourSize;
vm_size_t bytes;
vm_offset_t mapped;
vm_address_t logical;
IOByteCount pageOffset;
IOPhysicalLength segLen;
IOPhysicalAddress physAddr;
if( 0 == length)
length = getLength();
physAddr = getPhysicalSegment( sourceOffset, &segLen );
assert( physAddr );
pageOffset = physAddr - trunc_page( physAddr );
ourSize = length + pageOffset;
physAddr -= pageOffset;
logical = *atAddress;
if( 0 == (options & kIOMapAnywhere)) {
mapped = trunc_page( logical );
if( (logical - mapped) != pageOffset)
err = kIOReturnVMError;
}
if( kIOReturnSuccess == err)
err = vm_allocate( get_task_map(addressTask), &mapped, ourSize,
(options & kIOMapAnywhere)
? VM_ALLOCATE_ANYWHERE : VM_ALLOCATE_RESERVED);
if( err) {
kprintf("IOMemoryDescriptor::doMap: vm_allocate()"
" returned %08x\n", err);
return( err);
}
logical = mapped;
*atAddress = mapped + pageOffset;
segLen += pageOffset;
bytes = ourSize;
do {
// in the middle of the loop only map whole pages
if( segLen >= bytes)
segLen = bytes;
else if( segLen != trunc_page( segLen))
err = kIOReturnVMError;
if( physAddr != trunc_page( physAddr))
err = kIOReturnBadArgument;
#ifdef DEBUG
if( kIOLogMapping & gIOKitDebug)
kprintf("_IOMemoryMap::map(%x) %08x->%08x:%08x\n",
addressTask, mapped + pageOffset, physAddr + pageOffset,
segLen - pageOffset);
#endif
if( kIOReturnSuccess == err)
err = IOMapPages( get_task_map(addressTask), mapped, physAddr, segLen, options );
if( err)
break;
sourceOffset += segLen - pageOffset;
mapped += segLen;
bytes -= segLen;
pageOffset = 0;
} while( bytes
&& (physAddr = getPhysicalSegment( sourceOffset, &segLen )));
if( bytes)
err = kIOReturnBadArgument;
if( err)
doUnmap( addressTask, logical, ourSize );
else
mapped = true;
return( err );
}
IOReturn IOMemoryDescriptor::doUnmap(
task_t addressTask,
IOVirtualAddress logical,
IOByteCount length )
{
IOReturn err;
#ifdef DEBUG
if( kIOLogMapping & gIOKitDebug)
kprintf("IOMemoryDescriptor::doUnmap(%x) %08x:%08x\n",
addressTask, logical, length );
#endif
err = vm_deallocate( get_task_map(addressTask), logical, length );
assert( err == kIOReturnSuccess );
return( err );
}
IOReturn _IOMemoryMap::unmap( void )
{
IOReturn err;
if( logical && (0 == superMap)
&& (0 == (options & kIOMapStatic))) {
err = memory->doUnmap( addressTask, logical, length );
logical = 0;
} else
err = kIOReturnSuccess;
return( err );
}
void _IOMemoryMap::free()
{
unmap();
if( memory) {
LOCK;
memory->removeMapping( this);
UNLOCK;
memory->release();
}
if( superMap)
superMap->release();
super::free();
}
IOByteCount _IOMemoryMap::getLength()
{
return( length );
}
IOVirtualAddress _IOMemoryMap::getVirtualAddress()
{
return( logical);
}
task_t _IOMemoryMap::getAddressTask()
{
return( addressTask);
}
IOOptionBits _IOMemoryMap::getMapOptions()
{
return( options);
}
IOMemoryDescriptor * _IOMemoryMap::getMemoryDescriptor()
{
return( memory );
}
_IOMemoryMap * _IOMemoryMap::isCompatible(
IOMemoryDescriptor * owner,
task_t task,
IOVirtualAddress toAddress,
IOOptionBits _options,
IOByteCount _offset,
IOByteCount _length )
{
_IOMemoryMap * mapping;
if( addressTask != task)
return( 0 );
if( (options ^ _options) & (kIOMapCacheMask | kIOMapReadOnly))
return( 0 );
if( (0 == (_options & kIOMapAnywhere)) && (logical != toAddress))
return( 0 );
if( _offset < offset)
return( 0 );
_offset -= offset;
if( (_offset + _length) > length)
return( 0 );
if( (length == _length) && (!_offset)) {
retain();
mapping = this;
} else {
mapping = new _IOMemoryMap;
if( mapping
&& !mapping->init( owner, this, _offset, _length )) {
mapping->release();
mapping = 0;
}
}
return( mapping );
}
IOPhysicalAddress _IOMemoryMap::getPhysicalSegment( IOByteCount _offset,
IOPhysicalLength * length)
{
IOPhysicalAddress address;
LOCK;
address = memory->getPhysicalSegment( offset + _offset, length );
UNLOCK;
return( address );
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#undef super
#define super OSObject
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void IOMemoryDescriptor::initialize( void )
{
if( 0 == gIOMemoryLock)
gIOMemoryLock = IOLockAlloc();
}
void IOMemoryDescriptor::free( void )
{
if( _mappings)
_mappings->release();
super::free();
}
IOMemoryMap * IOMemoryDescriptor::setMapping(
task_t intoTask,
IOVirtualAddress mapAddress,
IOOptionBits options = 0 )
{
_IOMemoryMap * map;
map = new _IOMemoryMap;
LOCK;
if( map
&& !map->init( this, intoTask, mapAddress,
options | kIOMapStatic, 0, getLength() )) {
map->release();
map = 0;
}
addMapping( map);
UNLOCK;
return( map);
}
IOMemoryMap * IOMemoryDescriptor::map(
IOOptionBits options = 0 )
{
return( makeMapping( this, kernel_task, 0,
options | kIOMapAnywhere,
0, getLength() ));
}
IOMemoryMap * IOMemoryDescriptor::map(
task_t intoTask,
IOVirtualAddress toAddress,
IOOptionBits options,
IOByteCount offset = 0,
IOByteCount length = 0 )
{
if( 0 == length)
length = getLength();
return( makeMapping( this, intoTask, toAddress, options, offset, length ));
}
IOMemoryMap * IOMemoryDescriptor::makeMapping(
IOMemoryDescriptor * owner,
task_t intoTask,
IOVirtualAddress toAddress,
IOOptionBits options,
IOByteCount offset,
IOByteCount length )
{
_IOMemoryMap * mapping = 0;
OSIterator * iter;
LOCK;
do {
// look for an existing mapping
if( (iter = OSCollectionIterator::withCollection( _mappings))) {
while( (mapping = (_IOMemoryMap *) iter->getNextObject())) {
if( (mapping = mapping->isCompatible(
owner, intoTask, toAddress,
options | kIOMapReference,
offset, length )))
break;
}
iter->release();
if( mapping)
continue;
}
if( mapping || (options & kIOMapReference))
continue;
owner = this;
mapping = new _IOMemoryMap;
if( mapping
&& !mapping->init( owner, intoTask, toAddress, options,
offset, length )) {
IOLog("Didn't make map %08lx : %08lx\n", offset, length );
mapping->release();
mapping = 0;
}
} while( false );
owner->addMapping( mapping);
UNLOCK;
return( mapping);
}
void IOMemoryDescriptor::addMapping(
IOMemoryMap * mapping )
{
if( mapping) {
if( 0 == _mappings)
_mappings = OSSet::withCapacity(1);
if( _mappings && _mappings->setObject( mapping ))
mapping->release(); /* really */
}
}
void IOMemoryDescriptor::removeMapping(
IOMemoryMap * mapping )
{
assert( _mappings );
assert( _mappings->containsObject( mapping ) );
mapping->retain();
mapping->retain();
if( _mappings)
_mappings->removeObject( mapping);
}
// a minimalist serializer
bool IOMemoryDescriptor::serialize(OSSerialize *s) const
{
IOMemoryDescriptor * self = (IOMemoryDescriptor *) this;
OSNumber * off;
bool ok = false;
if( s->previouslySerialized(this)) return true;
off = OSNumber::withNumber(self->getPhysicalAddress(), IOPhysSize);
if( off)
ok = off->serialize(s);
if (off)
off->release();
return ok;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#undef super
#define super IOMemoryDescriptor
OSDefineMetaClassAndStructors(IOSubMemoryDescriptor, IOMemoryDescriptor)
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
bool IOSubMemoryDescriptor::initSubRange( IOMemoryDescriptor * parent,
IOByteCount offset, IOByteCount length,
IODirection withDirection )
{
if( !super::init())
return( false );
if( !parent)
return( false);
if( (offset + length) > parent->getLength())
return( false);
parent->retain();
_parent = parent;
_start = offset;
_length = length;
_direction = withDirection;
_tag = parent->getTag();
return( true );
}
void IOSubMemoryDescriptor::free( void )
{
if( _parent)
_parent->release();
super::free();
}
IOPhysicalAddress IOSubMemoryDescriptor::getPhysicalSegment( IOByteCount offset,
IOByteCount * length )
{
IOPhysicalAddress address;
IOByteCount actualLength;
assert(offset <= _length);
if( length)
*length = 0;
if( offset >= _length)
return( 0 );
address = _parent->getPhysicalSegment( offset + _start, &actualLength );
if( address && length)
*length = min( _length - offset, actualLength );
return( address );
}
void * IOSubMemoryDescriptor::getVirtualSegment(IOByteCount offset,
IOByteCount * lengthOfSegment)
{
return( 0 );
}
IOByteCount IOSubMemoryDescriptor::readBytes(IOByteCount offset,
void * bytes, IOByteCount withLength)
{
IOByteCount byteCount;
assert(offset <= _length);
if( offset >= _length)
return( 0 );
LOCK;
byteCount = _parent->readBytes( _start + offset, bytes,
min(withLength, _length - offset) );
UNLOCK;
return( byteCount );
}
IOByteCount IOSubMemoryDescriptor::writeBytes(IOByteCount offset,
const void* bytes, IOByteCount withLength)
{
IOByteCount byteCount;
assert(offset <= _length);
if( offset >= _length)
return( 0 );
LOCK;
byteCount = _parent->writeBytes( _start + offset, bytes,
min(withLength, _length - offset) );
UNLOCK;
return( byteCount );
}
IOReturn IOSubMemoryDescriptor::prepare(
IODirection forDirection = kIODirectionNone)
{
IOReturn err;
LOCK;
err = _parent->prepare( forDirection);
UNLOCK;
return( err );
}
IOReturn IOSubMemoryDescriptor::complete(
IODirection forDirection = kIODirectionNone)
{
IOReturn err;
LOCK;
err = _parent->complete( forDirection);
UNLOCK;
return( err );
}
IOMemoryMap * IOSubMemoryDescriptor::makeMapping(
IOMemoryDescriptor * owner,
task_t intoTask,
IOVirtualAddress toAddress,
IOOptionBits options,
IOByteCount offset,
IOByteCount length )
{
IOMemoryMap * mapping;
mapping = (IOMemoryMap *) _parent->makeMapping(
_parent, intoTask,
toAddress - (_start + offset),
options | kIOMapReference,
_start + offset, length );
if( !mapping)
mapping = super::makeMapping( owner, intoTask, toAddress, options,
offset, length );
return( mapping );
}
/* ick */
bool
IOSubMemoryDescriptor::initWithAddress(void * address,
IOByteCount withLength,
IODirection withDirection)
{
return( false );
}
bool
IOSubMemoryDescriptor::initWithAddress(vm_address_t address,
IOByteCount withLength,
IODirection withDirection,
task_t withTask)
{
return( false );
}
bool
IOSubMemoryDescriptor::initWithPhysicalAddress(
IOPhysicalAddress address,
IOByteCount withLength,
IODirection withDirection )
{
return( false );
}
bool
IOSubMemoryDescriptor::initWithRanges(
IOVirtualRange * ranges,
UInt32 withCount,
IODirection withDirection,
task_t withTask,
bool asReference = false)
{
return( false );
}
bool
IOSubMemoryDescriptor::initWithPhysicalRanges( IOPhysicalRange * ranges,
UInt32 withCount,
IODirection withDirection,
bool asReference = false)
{
return( false );
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */