IOUserSCSIParallelInterfaceController: Single-Segment Requirement Breaks No-DMA / No-IOMMU Controllers

Summary

I have a userspace IOUserSCSIParallelInterfaceController dext presenting a virtual disk. It's a software / no-DMA controller — it moves all data with a kernel CPU copy via UserGetDataBuffer; it never does hardware DMA and never reads fBufferIOVMAddr. Buffered I/O works and the disk mounts. The problem is unbuffered / raw-device I/O.

For a data-carrying task, ProcessParallelTask calls PrepareForDMA then GenerateIOVMSegments on the task's IODMACommand, and if the segment count != 1 it fails the task with EIO before ever calling UserProcessParallelTask / UserProcessBundledParallelTasks — so UserGetDataBuffer never gets a chance. I already use UserGetDataBuffer and it works for single-segment (aligned) tasks, but a client buffer that straddles a page boundary produces 2 segments and is rejected at that gate.

Concrete symptom

newfs_apfs writing the container superblock to the raw device from a page-straddling malloc'd buffer:

nx_format:308: failed to write superblock to block 0: 5 - Input/output error

Minimal repro against my raw device: an aligned 4 KiB pwrite succeeds; a 4 KiB pwrite from a buffer straddling a 16 KiB page returns errno 5.

What I think is going on (not sure)

My guess is that the framework expects a DART/IOMMU to coalesce a scattered buffer into one IOVM segment, and my virtual controller doesn't have one (ioreg shows no mapper and no iommu-parent on the node — attached), so GenerateIOVMSegments emits raw physical segments and a straddling buffer stays 2 segments. But I don't know if that's actually the reason, or whether a no-DMA controller is even supposed to go through GenerateIOVMSegments at all — hence the questions below.

Questions

  1. For a no-DMA controller that services data via UserGetDataBuffer, is there a supported way to make the framework deliver a task whose client buffer maps to more than one IOVM segment (relax/skip the GenerateIOVMSegments single-segment gate), so my upcall can run? UserGetDataBuffer returns a fresh contiguous buffer, so the original buffer's segment count shouldn't matter for a CPU-copy controller.

  2. Is there a characteristic to declare a PIO / no-DMA / software controller so the framework skips segment generation for it?

  3. Can a DriverKit controller get macOS to interpose an IOMapper/DART in front of it (so the buffer is coalesced into one segment) — via a property, a matching personality, or an intermediate provider nub? Or is a hardwareless controller simply not expected to support unbuffered/raw-device I/O?

Environment

  • Build: 26A5368g, arm64e
  • Full ioreg -w0 -r -c IOUserSCSIParallelInterfaceController attached.

Happy to file a Feedback with a sysdiagnose and the minimal repro.

IOUserSCSIParallelInterfaceController: Single-Segment Requirement Breaks No-DMA / No-IOMMU Controllers
 
 
Q