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!
First off, thank you for your patience with this. I wanted to make sure I'd gotten an authoritative answer on this, and it look a little while to get to the right person.
Next, my answer here is primarily about Apple Silicon support. I think the same code will also work on 64-bit Intel and I'm not going to try and sort out 32-bit Intel.
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.
So, first off, what doesn't matter here is the IOMemoryDescriptor configuration or inTaskWithPhysicalMask.Your trying to map 20Kb, so I would start testing with a size of 32Kb (2 pages). You could also specify a mask of:
0x0000 0000 FFFF F000ULL (32 bits, 16Kb aligned)
...but that's simply to start with the most straightforward configuration, not because other configuration won't work fine.
What DOES matter here is the "IOMapper" argument of your IODMACommand specification:
static OSPtr<IODMACommand>
withSpecification(SegmentFunction outSegFunc,
UInt8 numAddressBits,
UInt64 maxSegmentSize,
MappingOptions mappingOptions = kMapped,
UInt64 maxTransferSize = 0,
UInt32 alignment = 1,
IOMapper *mapper = NULL,
void *refCon = NULL);
Your passing in "NULL", but what you need to pass in is the IOMapper for your target I/O device, which you can retrieve by calling "IOMapper::copyMapperForDevice()".
What's going on here is that, Intel had a single global mapper and, since PPC days, IOMD physical addresses would get premapped, so "stuff worked" without driver changes, unless you specifically asked for unmapped addresses.
AppleSilicon doesn't have one DART but MANY (100+), so you need to tell IODMACommand what device you're targeting, so that the right DART does the mapping. Note that this also means that the segments returned by one IODMACommand aren't (necessarily) valid for a different device.
That also lets me explain this:
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.
As part of the transition to AppleSilicon and increased use of DARTs, the decision was made to essentially "unmap" the bottom 32 bits of physical memory in the same way (and for similar reasons) that VM systems do. This ensure that there are no valid physical addresses below 32GB, further interfering with the kind of memory descriptor "shortcuts" that worked on Intel/PPC.
On AppleSilicon, you MUST go through IODMACommand, passing in the correct mapper to get a working I/O address.
__
Kevin Elliott
DTS Engineer, CoreOS/Hardware