Memory Descriptors

Memory descriptors are implemented by the IOMemoryDescriptor class and is fundamental to working with memory in I/O Kit. The class also serves as a super class for other important memory-related classes, which we will discuss later in this chapter. Many parts of the I/O Kit accept an IOMemoryDescriptor as an argument. For example, the USB family uses the class to describe memory used for USB read and write requests.

The IOMemoryDescriptor describes the properties of a memory buffer or range of memory, but does not allocate (or free) the described memory. It contains metadata and allows some operations to be performed on the memory. It can describe virtual and physical memory. The class is versatile and can be used for a number of purposes. Consequently, there are also a number of ways to construct an IOMemoryDescriptor. A common way is to use the withAddressRange() method, as follows.

static IOMemoryDescriptor* withAddressRange(mach_vm_address_t address,
                                            mach_vm_size_t length, IOOptionBits options,
                                            task_t task);
  • The first argument, address, is the start address of the memory buffer the descriptor should operate on.
  • The length argument is the number of bytes of the buffer pointed to by address. The task argument specifies the task, which owns the virtual memory.
  • The options argument specifies the direction of the descriptor in the event that it is used for I/O transfers. It may affect the behaviour of prepare() and complete(). The following flags are possible:
    • kIODirectionNone
    • kIODirectionIn
    • kIODirectionOut
    • kIODirectionOutIn
    • kIODirectionInOut
  • The last paramter is the task that owns the memory. If the kernel owns the memory, you can pass kernel_task, which is a global variable pointing to the task_t structure for the kernel.

The options flags indicate the direction of an I/O transfer and may be used to determine if it is necessary to flush processor caches to ensure cache coherency.

If the descriptor is to be used for an I/O transfer you must first call its prepare() method, which will do the following:

  • page in memory, if the underlying memory is paged out
  • pin the memory down, so it cannot be paged out until the transfer is complete
  • configure device address translation mappings if necessary

images Caution Calls to prepare() must be balanced with a call to complete(). Care must also be taken not to call complete() unless prepare() was called first.

The prepare() method is not thread safe. However, it is valid to call prepare() multiple times, but you must then call complete() the same number of times. Calling the descriptors' release() method will not undo the effects of prepare() or call complete() for you, so complete() must be called before calling release(). If the descriptor is mapped into an address space, it will be unmapped automatically on release(). IOMemoryDescriptor can also be used to describe other types of memory, such as physical addresses. With physical addresses, the prepare() and complete() methods do nothing, but return successfully. Moreover, a physical memory descriptor is not associated with a task. The static member method withPhysicalAddress() can be used to construct an IOMemoryDescriptor for a physical segment, as in the following.

static IOMemoryDescriptor* withPhysicalAddress(IOPhysicalAddress address,  

IOByteCount withLength, IODirection withDirection);

The IOBufferMemoryDescriptor

The IOBufferMemoryDescriptor is a subclass of IOMemoryDescriptor, but unlike its super class, it also allocates memory. It is currently the preferred way of allocating memory intended to be mapped to user space or for performing device I/O from a kernel-allocated buffer. However, the allocation method used internally depends on the size of the request and the options passed during construction. The IOBufferMemoryDescriptor is also the preferred way for obtaining physically contiguous memory. IOBufferMemoryDescriptors can be allocated by the static factory method inTaskWithOptions() or inTaskWithPhysicalMask(), as follows.

static IOBufferMemoryDescriptor* inTaskWithOptions(
    task_t                       inTask,
    IOOptionBits                 options,
    vm_size_t                    capacity,
    vm_offset_t                  alignment = 1);

static IOBufferMemoryDescriptor* inTaskWithPhysicalMask(
    task_t                       inTask,
    IOOptionBits                 options,
    mach_vm_size_t               capacity,
    mach_vm_address_t            physicalMask);

The inTask argument specifies which task the memory should be mapped to. For a kernel buffer, this should be set to kernel_task. If you specify another task identifier, the memory will be allocated and reachable in that task's address space. In addition to the flags and options available to IOMemoryDescriptor, the following options can be passed to control the allocation behavior.

  • kIOMemoryPhysicallyContiguous allocates memory that is physically contiguous.
  • kIOMemoryPageable allocates memory that can be paged out. All memory is non-pageable by default.
  • kIOMemoryPurgeable applies only to pageable memory. If this option is specified, the memory pages can be discarded instead of paged out.
  • kIOMemoryKernelUserShared should be specified if the memory will be mapped into the kernel and a user space task. It ensures memory will be page-aligned.

The second way to construct an IOBufferMemoryDescriptor is via the inTaskWithPhysicalMask(), which allows one to specify a bit mask used to restrict the physical address range of the buffer. This is mainly useful when allocating memory for DMA for a device unable to access certain address ranges. For example, some older devices may be unable to access physical memory over 32 bits.

It is generally frowned upon to request physically contiguous memory, particularly after the system has booted, as the memory becomes fragmented quickly. This would make it difficult to find free contiguous buffers, particularly larger ones. Requesting contiguous memory may also result in some memory being paged out to handle the request, which can take a long time. Hardware devices generally support scatter/gather operations, where multiple smaller buffers are chained together in a list and passed to the device, which then reads the list to work out where in physical memory to find its data. Thus, contiguous memory is often unnecessary.

Just like the IOMalloc() family of functions, IOBufferMemoryDescriptor may sleep, so it should not be called from interrupt contexts or while holding simple locks. In fact, IOBufferMemoryDescriptor uses IOMalloc() and IOMallocAligned() internally to allocate memory.

Other Memory Descriptors

IOMemoryDescriptor has a number of other related subclasses, as follows.

  • IODeviceMemory is used to describe a range of memory mapped from a device.
  • IOMultiMemoryDescriptor can be used to represent a larger contiguous buffer consisting of smaller IOMemoryDescriptor objects.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.213.87