DEXT receives zero-filled buffer from DMA, despite firmware confirming data write

Hello everyone,

I am migrating a KEXT for a SCSI PCI RAID controller (LSI 3108 RoC) to DriverKit (DEXT). While the DEXT loads successfully, I'm facing a DMA issue: an INQUIRY command results in a 0-byte disk because the data buffer received by the DEXT is all zeros, despite our firmware logs confirming that the correct data was prepared and sent.

We have gathered detailed forensic evidence and would appreciate any insights from the community.

Detailed Trace of a Failing INQUIRY Command:

1, DEXT Dispatches the Command:

Our UserProcessParallelTask implementation correctly receives the INQUIRY task. Logs show the requested transfer size is 6 bytes, and the DEXT obtains the IOVA (0x801c0000) to pass to the hardware.

DEXT Log:

[UserProcessParallelTask_Impl] --- FORENSIC ANALYSIS ---
[UserProcessParallelTask_Impl] fBufferIOVMAddr          = 0x801c0000
[UserProcessParallelTask_Impl] fRequestedTransferCount  = 6

2, Firmware Receives IOVA and Prepares Correct Data:

A probe in our firmware confirms that the hardware successfully received the correct IOVA and the 6-byte length requirement. The firmware then prepares the correct 6-byte INQUIRY response in its internal staging buffer.

Firmware Logs:

-- [FIRMWARE PROBE: INCOMING DMA DUMP] --
  Host IOVA (High:Low) = 0x00000000801c0000
  DataLength in Header   = 6 (0x6)

--- [Firmware Outgoing Data Dump from go_inquiry] ---
Source Address: 0x228BB800, Length: 6 bytes
0x0000: 00 00 05 12 1F 00

3, Hardware Reports a Successful Transfer, but Data is Lost:

After the firmware initiates the DMA write to the Host IOVA, the hardware reports a successful transfer of 6 bytes back to our DEXT.

DEXT Completion Log:

[AME_Host_Normal_Handler_SCSI_Request] [TaskID: 200] COMPLETING...
[AME_Host_Normal_Handler_SCSI_Request] Hardware Transferred  = 6 bytes
[AME_Host_Normal_Handler_SCSI_Request]   - ReplyStatus         = SUCCESS (0x0)
[AME_Host_Normal_Handler_SCSI_Request]   - SCSIStatus          = SUCCESS (0x0)

The Core Contradiction:

Despite the firmware preparing the correct data and the hardware reporting a successful DMA transfer, the fDataBuffer in our DEXT remains filled with zeros. The 6 bytes of data are lost somewhere between the PCIe bus and host memory.

This "data-in-firmware, zeros-in-DEXT" phenomenon leads us to believe the issue lies in memory address translation or a system security policy, as our legacy KEXT works perfectly on the same hardware.

Compared to a KEXT, are there any known, stricter IOMMU/security policies for a DEXT that could cause this kind of "silent write failure" (even with a correct IOVA)?

Alternatively, what is the correct and complete expected workflow in DriverKit for preparing an IOMemoryDescriptor* fDataBuffer (received in UserProcessParallelTask) for a PCI hardware device to use as a DMA write target?

Any official documentation, examples, or advice on the IOMemoryDescriptor to PCI Bus Address workflow would be immensely helpful.

Thank you.

Charles

Answered by DTS Engineer in 862867022

While the DEXT loads successfully, I'm facing a DMA issue: an INQUIRY command results in a 0-byte disk because the data buffer received by the DEXT is all zeros, despite our firmware logs confirming that the correct data was prepared and sent.

How is your DEXT getting access to the transfer data? SCSIControllerDriverKit was intentionally architected to make that very difficult.

Compared to a KEXT, are there any known, stricter IOMMU/security policies for a DEXT that could cause this kind of "silent write failure" (even with a correct IOVA)?

No, not really.

However, I think you've confused things a bit here:

Alternatively, what is the correct and complete expected workflow in DriverKit for preparing an IOMemoryDescriptor* fDataBuffer (received in UserProcessParallelTask) for a PCI hardware device to use as a DMA write target?

My concern here is where you say:

...for preparing an IOMemoryDescriptor* fDataBuffer

The problem is that the whole "goal" of UserProcessParallelTask is to AVOID giving you an IOMemoryDescriptor. What you're actually given is "fBufferIOVMAddr", which is a "raw" physical address ready to be passed over to the PCI bus. The way this is supposed to work is that you pass that address to your card (doing whatever is necessary to break up the transfer), it does the DMA, and you then tell the kernel "done now". That completes the entire transfer without your DEXT ever having access to the actual data.

NOW, you do have UserGetDataBuffer, but we don't really want you to use it. Quoting the header documentation:

"The dext class can call this method inside UserProcessParallelTask to get the data buffer associated with that IO request. Only call this method if access to the data buffer is required. Calling this method can have a significant impact on performance. The caller will have to prepare new DMA mappings for this buffer and can no longer use the mappings in fBufferIOVMAddr."

If you call UserGetDataBuffer, then you're taking over the entire DMA process and it's now your responsibility to "get" the data into the memory descriptor you're given. That also means that "fBufferIOVMAddr" is no longer useful.

That leads to here:

Despite the firmware preparing the correct data and the hardware reporting a successful DMA transfer, the fDataBuffer in our DEXT remains filled with zeros.

This is just a guess, but I suspect what you actually did in UserProcessParallelTask was:

  • Call UserGetDataBuffer, thinking that would let you "see" the data.

  • Did your DMA transfer using fBufferIOVMAddr.

However, that meant what actually happened was:

  • The DMA to fBufferIOVMAddr completed as expected.

  • You called UserCompleteParallelTask.

  • The kernel copied the contents of the IOMD returned by UserGetDataBuffer into the fBufferIOVMAddr.

...zeroing out the data that was just transferred. __
Kevin Elliott
DTS Engineer, CoreOS/Hardware

While the DEXT loads successfully, I'm facing a DMA issue: an INQUIRY command results in a 0-byte disk because the data buffer received by the DEXT is all zeros, despite our firmware logs confirming that the correct data was prepared and sent.

How is your DEXT getting access to the transfer data? SCSIControllerDriverKit was intentionally architected to make that very difficult.

Compared to a KEXT, are there any known, stricter IOMMU/security policies for a DEXT that could cause this kind of "silent write failure" (even with a correct IOVA)?

No, not really.

However, I think you've confused things a bit here:

Alternatively, what is the correct and complete expected workflow in DriverKit for preparing an IOMemoryDescriptor* fDataBuffer (received in UserProcessParallelTask) for a PCI hardware device to use as a DMA write target?

My concern here is where you say:

...for preparing an IOMemoryDescriptor* fDataBuffer

The problem is that the whole "goal" of UserProcessParallelTask is to AVOID giving you an IOMemoryDescriptor. What you're actually given is "fBufferIOVMAddr", which is a "raw" physical address ready to be passed over to the PCI bus. The way this is supposed to work is that you pass that address to your card (doing whatever is necessary to break up the transfer), it does the DMA, and you then tell the kernel "done now". That completes the entire transfer without your DEXT ever having access to the actual data.

NOW, you do have UserGetDataBuffer, but we don't really want you to use it. Quoting the header documentation:

"The dext class can call this method inside UserProcessParallelTask to get the data buffer associated with that IO request. Only call this method if access to the data buffer is required. Calling this method can have a significant impact on performance. The caller will have to prepare new DMA mappings for this buffer and can no longer use the mappings in fBufferIOVMAddr."

If you call UserGetDataBuffer, then you're taking over the entire DMA process and it's now your responsibility to "get" the data into the memory descriptor you're given. That also means that "fBufferIOVMAddr" is no longer useful.

That leads to here:

Despite the firmware preparing the correct data and the hardware reporting a successful DMA transfer, the fDataBuffer in our DEXT remains filled with zeros.

This is just a guess, but I suspect what you actually did in UserProcessParallelTask was:

  • Call UserGetDataBuffer, thinking that would let you "see" the data.

  • Did your DMA transfer using fBufferIOVMAddr.

However, that meant what actually happened was:

  • The DMA to fBufferIOVMAddr completed as expected.

  • You called UserCompleteParallelTask.

  • The kernel copied the contents of the IOMD returned by UserGetDataBuffer into the fBufferIOVMAddr.

...zeroing out the data that was just transferred. __
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Hello Kevin,

Thank you for your analysis. We have reviewed our implementation and can confirm that we are not calling UserGetDataBuffer in our UserProcessParallelTask_Impl. Our DEXT fully adheres to the official recommendation you described, using the framework-provided fBufferIOVMAddr for the DMA operation.

Since that was not the issue, we performed a deeper comparison between our working KEXT and the DEXT. We discovered that in its InitializeController() method, the KEXT explicitly declares its DMA capabilities to the I/O Kit storage stack by setting several properties. The code is as follows:

// In KEXT's InitializeController()
setProperty(kIOMaximumSegmentCountReadKey,  (UInt64)MAX_IO_SG, 64);
setProperty(kIOMaximumSegmentCountWriteKey, (UInt64)MAX_IO_SG, 64);
setProperty(kIOMaximumByteCountReadKey,     (UInt64)MAX_IO_LENGTH, 64);
setProperty(kIOMaximumByteCountWriteKey,    (UInt64)MAX_IO_LENGTH, 64);
setProperty(kIOPropertyPhysicalInterconnectLocationKey, kIOPropertyExternalKey);

It's possible that the absence of these property settings in our DEXT is what causes the SCSI framework to fail when preparing the IOMMU mapping for the INQUIRY command's buffer, leading to the silent DMA failure.

Our question now is what the correct method is for implementing this in DriverKit. We found the IOPCIDevice::SetProperties(OSDictionary *) method, and we have also seen these property keys mentioned in DriverKit-related documentation. However, the key constants themselves (e.g., kIOMaximumSegmentCountReadKey) do not appear to be defined in any available DriverKit SDK header, which prevents our code from compiling.

Could you please advise us on the official, recommended way for a DEXT to set these storage-specific properties for the framework? Are we missing a specific header file, or is the expected approach to manually define these key strings in our source code?

Thank you again for your valuable assistance.

Charles

DEXT receives zero-filled buffer from DMA, despite firmware confirming data write
 
 
Q