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
UserGetDataBufferthe 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 atfBufferIOVMAddr, 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
UserGetDataBufferruns? 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
fTransferDirectionby 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.