# IOUserSCSIParallelInterfaceController: `UserGetDataBuffer` returns a zero-filled buffer for some writes

I'm writing a software IOUserSCSIParallelInterfaceController in DriverKit — there's no DMA hardware behind it (it forwards commands elsewhere), so for each command I read the payload on the CPU by calling UserGetDataBuffer() from within my task-processing method and then CreateMapping() on the returned IOBufferMemoryDescriptor.

This works almost all the time, but I've got an intermittent case I can't explain: for some WRITE tasks the buffer I get back is entirely zero-filled, even though it's a genuine write that should carry data. In those cases the SCSIUserParallelTask.fTransferDirection I'm handed is kSCSIDataTransfer_FromInitiatorToTarget, so the task itself looks like a perfectly normal write to me. It seems to happen when the same task object gets reused — a read on that task, then a write.

I got stuck, so I disassembled IOSCSIParallelFamily to try to understand where the buffer comes from. In UserGetDataBuffer_Impl it looks like it allocates a fresh IOBufferMemoryDescriptor, zero-fills it, and only copies the client data in when the transfer direction is "from initiator to target". Roughly what I think I'm seeing:

; allocate a fresh IOBufferMemoryDescriptor, then bzero it
bl   GetDataTransferDirection      ; SCSIParallelTask -> SCSITask (+0x100), then ldrb w0, [x0, #0x5b]
cmp  w0, #1                        ; kSCSIDataTransfer_FromInitiatorToTarget ?
b.ne Lskip                         ; if not a write, leave the buffer zeroed
...  client->readBytes(0, bounce, len)   ; copy client -> bounce
Lskip:
...  return bounce                 ; hand back the (possibly still-zeroed) buffer

The direction it tests there is read from the SCSITask itself (the byte at SCSITask+0x5b, via GetDataTransferDirection), not from the fTransferDirection field I get in the task struct. And in the failing cases that byte still seems to hold the previous direction for that task (a read, 0x02), so the cmp w0, #1 doesn't match and the copy is skipped — which would explain the zero buffer.

I could easily be misreading the disassembly, so I'd really appreciate a sanity check on the intended contract:

  • Is UserGetDataBuffer the right way for a software (no-DMA) controller to get at write payload on the CPU at all? The docs frame it as the exception and otherwise point at fBufferIOVMAddr, but that reads like an IOVM/physical segment I don't think I can map for CPU access — is there a CPU-accessible path I'm missing?
  • Is it my responsibility (or the layer above me) to make sure the task's data-transfer direction is established before UserGetDataBuffer runs? Is there something I should be doing at task setup / UserMapHBAData / completion so this direction isn't stale when a task object is recycled?
  • Or is the SCSITask direction meant to always agree with fTransferDirection by the time my task-processing method runs, and a mismatch means I've done something wrong on my end?

Any pointers on the intended behavior here would be a big help — thanks.

# IOUserSCSIParallelInterfaceController: `UserGetDataBuffer` returns a zero-filled buffer for some writes
 
 
Q