MacOS(Apple Silicon) IOKit driver for FPGA DMA transmission, kernel panic.

MacOS(Apple Silicon) IOKit driver for FPGA DMA transmission, kernel panic.

Hardware and software configuration: MAC mini M1 2020 16GB, macOS Ventura 13.0 or 13.7.8 FPGA device capability: 64-bit

Complete description: We've developed a DMA driver for PCIe devices (FPGA) based on IOKit. The driver can start normally through kextload, and the bar mapping, DMA registers, etc. are all correct. I am testing DMA data transmission, but a kernel panic has occurred. The specific content of the panic is as follows: {"bug_type":"210","timestamp":"2026-01-28 14:35:30.00 +0800","os_version":"macOS 13.0 (22A380)","roots_installed":0,"incident_id":"61C9B820-8D1B-4E75-A4EB-10DC2558FA75"} { "build" : "macOS 13.0 (22A380)", "product" : "Macmini9,1", "socId" : "0x00008103", "kernel" : "Darwin Kernel Version 22.1.0: Sun Oct 9 20:14:30 PDT 2022; root:xnu-8792.41.9~2/RELEASE_ARM64_T8103", "incident" : "61C9B820-8D1B-4E75-A4EB-10DC2558FA75", "crashReporterKey" : "6435F6BD-4138-412A-5142-83DD7E5B4F61", "date" : "2026-01-28 14:35:30.16 +0800",

"panicString" : "panic(cpu 0 caller 0xfffffe0026c78c2c): "apciec[pcic0-bridge]::handleInterrupt: Request address is greater than 32 bits linksts=0x99000001 pcielint=0x02220060 linkcdmsts=0x00000000 (ltssm 0x11=L0)\n" @AppleT8103PCIeCPort.cpp:1301\nDebugger message: panic\nMemory ID: 0x6\nOS release type: User\nOS version: 22A380\nKernel version: Darwin Kernel Version 22.1.0: Sun Oct 9 20:14:30 PDT 2022; root:xnu-8792.41.9~2/RELEASE_ARM64_T8103\nFileset Kernelcache UUID: C222B4132B9708E5E0E2E8B8C5896410\nKernel UUID: 0BFE6A5D-118B-3889-AE2B-D34A0117A062\nBoot session UUID: 61C9B820-8D1B-4E75-A4EB-10DC2558FA75\niBoot version: iBoot-8419.41.10\nsecure boot?: YES\nroots installed: 0\nPaniclog version: 14\nKernelCache slide: 0x000000001d1b4000\nKernelCache base: 0xfffffe00241b8000\nKernel slide: 0x000000001e3f8000\nKernel text base: 0xfffffe00253fc000\nKernel text exec slide: 0x000000001e4e0000\nKernel text exec base: 0xfffffe00254e4000\nmach_absolute_time: 0x907c3082\nEpoch Time: sec usec\n Boot : 0x6979adbb 0x00023a6a\n Sleep : 0x00000000 0x00000000\n Wake : 0x00000000 0x00000000\n Calendar: 0x6979ae1a 0x00064953\n\nZone info:\n Zone map: 0xfffffe1000834000 - 0xfffffe3000834000\n . VM : 0xfffffe1000834000 - 0xfffffe14cd500000\n . RO : 0xfffffe14cd500000 - 0xfffffe1666e98000\n . GEN0 : 0xfffffe1666e98000 - 0xfffffe1b33b64000\n . GEN1 : 0xfffffe1b33b64000 - 0xfffffe2000830000\n . GEN2 : 0xfffffe2000830000 - 0xfffffe24cd4fc000\n . GEN3 : 0xfffffe24cd4fc000 - 0xfffffe299a1c8000\n . DATA : 0xfffffe299a1c8000 - 0xfffffe3000834000\n Metadata: 0xfffffe3f4d1ac000 - 0xfffffe3f551ac000\n Bitmaps : 0xfffffe3f551ac000 - 0xfffffe3f5ac94000\n\nCORE 0 recently retired instr at 0xfffffe002569d7a0\nCORE 1 recently retired instr at 0xfffffe002569eea0\nCORE 2 recently retired instr at 0xfffffe002569eea0\nCORE 3 recently retired instr at 0xfffffe002569eea0\nCORE 4 recently retired instr at 0xfffffe002569eea0\nCORE 5 recently retired instr at 0xfffffe002569eea0\nCORE 6 recently retired instr at 0xfffffe002569eea0\nCORE 7 recently retired instr at 0xfffffe002569eea0\nTPIDRx_ELy = {1: 0xfffffe2000c23010 0: 0x0000000000000000 0ro: 0x0000000000000000 }\nCORE 0 PVH locks held: None\nCORE 1 PVH locks held: None\nCORE 2 PVH locks held: None\nCORE 3 PVH locks held: None\nCORE 4 PVH locks held: None\nCORE 5 PVH locks held: None\nCORE 6 PVH locks held: None\nCORE 7 PVH locks held: None\nCORE 0 is the one that panicked. Check the full backtrace for details.\nCORE 1: PC=0xfffffe00279db94c, LR=0xfffffe00260d5d9c, FP=0xfffffe8ffecaf850\nCORE 2: PC=0xfffffe0025be76b0, LR=0xfffffe0025be7628, FP=0xfffffe8fff08f5f0\nCORE 3: PC=0x00000001c7cacd78, LR=0x00000001c7cacd84, FP=0x000000016f485130\nCORE 4: PC=0xfffffe002557f55c, LR=0xfffffe002557f55c, FP=0xfffffe8ffe1dff00\nCORE 5: PC=0xfffffe002557f55c, LR=0xfffffe002557f55c, FP=0xfffffe8fff5eff00\nCORE 6: PC=0xfffffe002557f55c, LR=0xfffffe002557f55c, FP=0xfffffe8ffed8bf00\nCORE 7: PC=0xfffffe002557f55c, LR=0xfffffe002557f55c, FP=0xfffffe8fff11bf00\nCompressor Info: 0% of compressed pages limit (OK) and 0% of segments limit (OK) with 0 swapfiles and OK swap space\nPanicked task 0xfffffe1b33aad678: 0 pages, 470 threads: pid 0: kernel_task\nPanicked thread: 0xfffffe2000c23010, backtrace: 0xfffffe8fff6eb6a0, tid: 265\n\t\t ...

Kernel Extensions in backtrace:\n com.apple.driver.AppleT8103PCIeC(1.0)[A595D104-026A-39E5-93AA-4C87CE8C14D2]@0xfffffe0026c619d0->0xfffffe0026c86c97\n dependency: com.apple.driver.AppleARMPlatform(1.0.2)[11A9713E-6739-3A4C-8571-2D8EAA062278]@0xfffffe0025f13ff0->0xfffffe0025f6255f\n dependency: com.apple.driver.AppleEmbeddedPCIE(1)[E71CBCCD-AEB8-3E7B-933D-4FED4241BF13]@0xfffffe002654e0b0->0xfffffe00265684c7\n dependency: com.apple.driver.ApplePIODMA(1)[A419BABC-A7A3-316D-A150-7C2C2D1F6D53]@0xfffffe00269a24b0->0xfffffe00269a6c3b\n dependency: com.apple.driver.IODARTFamily(1)[03997E20-8A3F-3412-A4E8-BD968A75A07D]@0xfffffe00275bcf50->0xfffffe00275d0a3f\n dependency: com.apple.iokit.IOPCIFamily(2.9)[EC78F47B-530B-3F87-854E-0A0A5FD9BBB2]@0xfffffe0027934350->0xfffffe002795f3d3\n dependency: com.apple.iokit.IOReportFamily(47)[843B39D3-146E-3992-B7C7-960148685DC8]@0xfffffe0027963010->0xfffffe0027965ffb\n dependency: com.apple.iokit.IOThunderboltFamily(9.3.3)[B22BC005-BB7B-32A3-99C0-39F3BDBD8E54]@0xfffffe0027a5e3f0->0xfffffe0027b9a1a3\n\nlast started kext at 1915345919: com.sobb.pcie-dma\t1.0.0d1 (addr 0xfffffe00240e47f0, size 9580)\nlast stopped kext at 1774866338: com.sobb.pcie-dma\t1.0.0d1 (addr 0xfffffe00240e47f0, size 9580)\nloaded

It seems that the DMA request address initiated by FPGA exceeded 32 bits, which was intercepted by PCIe root port and resulted in a kernel panic.This is also the case on macOS (M2).

I have tried the following code interface: IOBufferMemoryDescriptor: a. withCapacity(bufferSize, kIODirectionInOut, true); b. inTaskWithPhysicalMask(kernel_task, kIODirectionInOut, bufferSize, 0x00000000FFFFFFFFULL)。 The physical addresses of the constructed descriptors are all >32 bits; IODMACommand: a. withSpecification(kIODMACommandOutputHost64, 64, 0, IODMACommand::kMapped, 0, 0),gen64IOVMSegments() The allocated IOVM address must be>32 bits, which will generate a kernel panic when used later. b.withSpecification(kIODMACommandOutputHost32, 32, 0, IODMACommand::kMapped, 0, 0),gen32IOVMSegments() The allocation of IOVM failed with error code kIOReturnenMessageTooLarge.

So after the above attempts, the analysis shows that the strategy of Dart+PCIe root port on macOS (Apple Silicon) is causing the failure of 64 bit DMA address transfer.

I have two questions: a. Does Dart in macOS (Apple Silicon) definitely not allocate <=32-bit IOVM addresses? b. Is there any other way to achieve DMA transfer for FGPA devices on macOS (Apple Silicon)?

Thanks!

It seems that the DMA request address initiated by FPGA exceeded 32 bits, which was intercepted by PCIe root port and resulted in a kernel panic.This is also the case on macOS (M2).

Please file a bug on this, upload the full panic report there, and then post the bug number back her. I'd like to get a clearer picture of what actually failed.

I'm also a bit confused by this point:

It seems that the DMA request address initiated by FPGA exceeded 32 bits,

That address should have come from your driver, so how did it exceed 32 bits? All of our APIs for retrieving a bus address should either provide an address that meets your specification or fail entirely. More to the point, even if they some how did return a larger address, why/how would your driver have written a larger address when it was only prepared to handle a 32 bit address?

Next, the big question is how much memory are you actually trying to map? And have you done any experimentation with smaller mappings?

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Hi Kevin,

filed bug number is FB21888307. I provide partial code on bug.

Reply to your question:

That address should have come from your driver, so how did it exceed 32 bits? All of our APIs for retrieving a bus address should either provide an address that meets your specification or fail entirely. More to the point, even if they some how did return a larger address, why/how would your driver have written a larger address when it was only prepared to handle a 32 bit address?

Yes, address comes from my driver by gen64IOVMSegments API. And at the beginning, I didn't know that the address allocated through the API were always >32 bit, so when I received an address >32 bit, I didn't directly return, but continued to use it, resulting in a kernel panic.

Next, the big question is how much memory are you actually trying to map? And have you done any experimentation with smaller mappings?

I trying to map 20 KB memory, I have already tried 1 Byte or 4 KB, still get 64-bit address. And maximum DMA data size would set to 8 MB, but I haven't set 8M yet.

Thanks!

I trying to map 20 KB memory, I have already tried 1 Byte or 4 KB, still get 64-bit address. And maximum DMA data size would set to 8 MB, but I haven't set 8M yet.

I haven't checked the code, but I'd expect that you'd need to be page aligned, with would be 16KB (not 4KB). I'm actually not sure why gen64IOVMSegments worked, though it's possible that using the "native" address size changed something. Ironically, I think this might have "just worked" if you'd gone straight to 8MB

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Hi Kevin,

I haven't checked the code, but I'd expect that you'd need to be page aligned, with would be 16KB (not 4KB). I'm actually not sure why gen64IOVMSegments worked, though it's possible that using the "native" address size changed something. Ironically, I think this might have "just worked" if you'd gone straight to 8MB

Let me summarize my current attempts:

IODMACommand uses 1B, 4KB, or 16KB page alignment, and allocates IOVM address >32 bits by gen64IOVMSegments(). If the DMA transfer process continues, a kernel panic will occur. PanicSting is apciec[pcic0-bridge]::handleInterrupt: Request address is greater than 32 bits.

Using gen32IOVMSegments() will return kIOReturnMessage TooLarge. If I don't set the maximum value of 8M, the result will still be the same, so I think it may not be related to the maximum value.

Based on my current attempts, I am focusing on these two issues:

  1. Does Apple M silicon not allocate<=32-bit bus addresses to third-party PCIe device (FPGA)?
  2. Can Apple M silicon be configured with apciec to not intercept DMA address requests with more than 32 bits?

Using gen32IOVMSegments() will return kIOReturnMessageTooLarge. If I don't set the maximum value of 8M, the result will still be the same, so I think it may not be related to the maximum value.

If you haven't already, take a look at my post here. I haven't checked the code to confirm, but I strongly suspect that calling gen32IOVMSegments with a mask of the wrong size (like 0xFFFFF000) will cause kIOReturnMessageTooLarge, as the true mask range you've specified ("0x00000000FFFFF000") is too large to fit in 32 bits.

Does Apple M silicon not allocate <=32-bit bus addresses to third-party PCIe devices (FPGA)?

Quite the opposite. We've actually gone to considerable trouble to ensure that we COULD provide 32-bit addresses to the PCI bus. Quoting "Address Translation on 64-Bit System Architectures":

"Apple solved the problem with address translation which “maps” blocks of memory into the 32-bit address space of a PCI device. In this scheme, the PCI device still sees a 4-gigabyte space, but that space can be made up of noncontiguous blocks of memory. A part of the memory controller called the DART (device address resolution table) translates between the PCI address space and the much larger main memory address space. The DART handles this by keeping a table of translations to use when mapping between the physical addresses the processor sees and the addresses the PCI device sees (called I/O addresses)."

Note that the DART is common to all of ARM machines and, in fact, many of our more recent architectural choices basically require it to exist. For example, as I talked about here, many of our drivers now assume that IODMACommand will simply never return multiple segments.

That leads to here:

Can Apple M silicon be configured with APIC to not intercept DMA address requests with more than 32 bits?

First, as background context, part of what the DART changes is that it breaks the direct link between "physical addresses" (meaning, the linear address range that represents the actual position of data in physical RAM) and "I/O addresses" (meaning, the address you put on the PCI bus). True physical addresses are effectively useless outside of the VM system (notably, you CANNOT use one on the PCI bus), which is why:

"Because there is not a one-to-one correspondence between physical addresses and I/O addresses, physical addresses obtained from the pmap layer have no purpose outside the virtual memory system itself. Drivers that use pmap calls (such as pmap_extract) to get such addresses will fail to work on systems with a DART. To prevent the use of these calls, OS X v10.3 will refuse to load a kernel extension that uses them, even on systems without a DART.

With that context, returning to here:

Can Apple M silicon be configured with apciec to not intercept DMA address requests with more than 32 bits?

Well, no, but that's because the question doesn't really make sense. As I talked about above, "I/O addresses" do not directly map to physical memory; the DART is translating all addresses that cross the bus, regardless of address size.

In any case, I think the issue here is the address masking issue I mentioned at the top.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

If you haven't already, take a look at my post here. I haven't checked the code to confirm, but I strongly suspect that calling gen32IOVMSegments with a mask of the wrong size (like 0xFFFFF000) will cause kIOReturnMessageTooLarge, as the true mask range you've specified ("0x00000000FFFFF000") is too large to fit in 32 bits.

Yes, I am currently using the inTaskWithPhysicalMask interface in my code, and the masks I have tried are 0 x0000 0000 FFFF FFFFULL,0x0000 0000 FFFF F000ULL, 0xFFFF FFFF,0xFFFF F000, and 0x0, but the result is the same, using gen32IOVMSegments will return the same error. I have also used IOBufferCacheDescriptors:: withOptions and IOBufferCacheDescriptors:: withCapacity to build memory descriptors, but the results have not changed much.

If you have time, Could you take a look at the code? I have submitted on the bug. One more thing to add: I have also tried using the prepare() interface of IODMACommand, but there have been no significant changes.

Thanks!!

MacOS(Apple Silicon) IOKit driver for FPGA DMA transmission, kernel panic.
 
 
Q