IOMemoryMap::wireRange() should be supported KPI and documented

Originator:phil
Number:rdar://25140509 Date Originated:14-Mar-2016 12:01 PM
Status:Open Resolved:
Product:OS X SDK Product Version:10.10+
Classification:Other Reproducible:Always
 
Summary:
After some months of repeatedly looking into a strange page fault issue in a client's kext, I have finally solved the problem using IOMemoryMap::wireRange() after reading a bunch of related xnu source code. For reasons that are unclear, wireRange() has no headerdocs, and is in the com.apple.kpi.unsupported KPI. I don't see a way of solving the issue using a supported KPI, so it seems the function should be promoted to the IOKit KPI.

The issue we were having was the following:
- IOKit IOUserClient subclass has an external method with a variable length struct input.
- Above a certain size of input buffer, this is passed as an IOMemoryDescriptor; to directly access it from kernel code, we called createMappingInTask() with kernel_task as the task and kIOMapAnywhere for options.
- Because we need to access the buffer from a critical section of kernel code where page faults are problematic, we prepare()d the memory descriptor. (It did not seem to matter if this was before or after the createMappingInTask() call)
- We were still hitting page faults when accessing the buffer.

As it turns out, createMappingInTask seems to be lazy since OS X 10.10, and does not actually update the live page table. Presumably this is an optimisation to defer TLB flushes? (NB This seems questionable as access to each individual page will trigger a page fault AND a TLB flush, so this approach will cost more if there is more than 1 page involved, unless I've missed something.)

createMappingInTask() accepts a kIOMapPrefault argument to work around this issue. Unfortunately, this doesn't work if mapping into the kernel task. (Why not?)

Rooting around in the xnu source, I found that vm_map_wire() can be used to pre-fault pages, and although we can't call it directly from a 3rd party kext, IOMemoryMap::wireRange() will call it for us. Doing that solves our problem.

However, now we need to depend on the unsupported KPI.

Steps to Reproduce:
1. In a kext's IOUserClient subclass's external method implementation, check that the structureInputDescriptor of the external method arguments struct is non-NULL.
2. Map the described user buffer into the kernel task's virtual address space using createMappingInTask().
3. The mapping will not update CR3, so accesses to addresses in the range will trigger page faults. To avoid this, call wireRange() with appropriate arguments to pre-fault the kernel mapping.

Expected Results:
As IOMemoryMap is part of I/O Kit, the kext should link & load as before.

I would also expect an explanation of wireRange's behaviour in the header declaration.

Actual Results:
Loading the kext fails until we add a dependency on the 'unsupported' KPI to the kext.

There is no documentation on wireRange(), or any other way of pre-faulting a kernel-mapped memory range.

Version:
The call to IOMemoryMap::wireRange() appears to be necessary in this specific case from OS X 10.10 onwards. We never hit those page faults on 10.9.

Notes:
If there is an alternative way to pre-fault thusly kernel-mapped memory descriptors, using only public KPIs, I would of course love to know about it.

Configuration:
N/A

Attachments:

Comments


Please note: Reports posted here will not necessarily be seen by Apple. All problems should be submitted at bugreport.apple.com before they are posted here. Please only post information for Radars that you have filed yourself, and please do not include Apple confidential information in your posts. Thank you!