Anurag
Anurag

Reputation: 661

How to get the physical address in macosx kernel for a virtual address for a particular process?

I was wondering if there is an existing system call/API for accessing getting the physical address of the virtual address? If there is none then some direction on how to get that working ?

Also, how to get the physical address of MMIO which is non-pageable physical memory ?

Upvotes: 3

Views: 1836

Answers (1)

pmdj
pmdj

Reputation: 23438

The answer lies in IOMemoryDescriptor and IODMACommand objects.

If the memory in question is kernel-allocated, it should be allocated by creating an IOBufferMemoryDescriptor in the first place. If that's not possible, or if it's a buffer allocated in user space, you can wrap the relevant pointer using IOMemoryDescriptor::withAddressRange(address, length, options, task) or one of the other factory functions. In the case of withAddressRange, the address passed in must be virtual, in the address space of task.

You can directly grab physical address ranges from an IOMemoryDescriptor by calling the getPhysicalSegment() function (only valid between prepare()complete() calls). However, normally you would do this for creating scatter-gather lists (DMA), and for this purpose Apple strongly recommends the IODMACommand. You can create these using IODMACommand::withSpecification(). Then use the genIOVMSegments() function to generate the scatter-gather list.

Modern Macs, and also some old PPC G5s contain an IOMMU (Intel calls this VT-d), so the system memory addresses you pass to PCI/Thunderbolt devices are not in fact physical, but IO-Mapped. IODMACommand will do this for you, as long as you use the "system mapper" (the default) and set mappingOptions to kMapped. If you're preparing addresses for the CPU, not a device, you will want to turn off mapping - use kIOMemoryMapperNone in your IOMemoryDescriptor options. Depending on what exactly you're trying to do, you probably don't need IODMACommand in this case either.

Note: it's often wise to pool and reuse your IODMACommand objects, rather than freeing and reallocating them.

Regarding MMIO, I assume you mean PCI BARs and similar - for IOPCIDevice, you can grab an IOMemoryDescriptor representing the memory-mapped device range using getDeviceMemoryWithRegister() and similar functions.

Example:

If all you want are pure CPU-space physical addresses for a given virtual memory range in some task, you can do something like this (untested as a complete kext that uses it would be rather large):

// INPUTS:
mach_vm_address_t virtual_range_start = …; // start address of virtual memory
mach_vm_size_t virtual_range_size_bytes = …; // number of bytes in range
task_t task = …; // Task object of process in which the virtual memory address is mapped
IOOptionBits direction = kIODirectionInOut; // whether the memory will be written or read, or both during the operation
IOOptionBits options =
    kIOMemoryMapperNone  // we want raw physical addresses, not IO-mapped
    | direction;

// Process for getting physical addresses:

IOMemoryDescriptor* md = IOMemoryDescriptor::withAddressRange(
    virtual_range_start, virtual_range_size_bytes, direction, task);
// TODO: check for md == nullptr

// Wire down virtual range to specific physical pages
IOReturn result = md->prepare(direction);
// TODO: do error handling

IOByteCount offset = 0;
while (offset < virtual_range_size_bytes)
{
    IOByteCount segment_len = 0;
    addr64_t phys_addr = md->getPhysicalSegment(offset, &len, kIOMemoryMapperNone);
    // TODO: do something with physical range of segment_len bytes at address phys_addr here

    offset += segment_len;
}

/* Unwire. Call this only once you're done with the physical ranges
 * as the pager can change the physical-virtual mapping outside of
 * prepare…complete blocks. */
md->complete(direction);
md->release();

As explained above, this is not suitable for generating DMA scatter-gather lists for device I/O. Note also this code is only valid for 64-bit kernels. You'll need to be careful if you still need to support ancient 32-bit kernels (OS X 10.7 and earlier) because virtual and physical addresses can still be 64-bit (64-bit user processes and PAE, respectively), but not all memory descriptor functions are set up for that. There are 64-bit-safe variants available to be used for 32-bit kexts.

Upvotes: 4

Related Questions