Build a Cross-Platform PCIe Driver for Windows, Linux, and macOS on x64 and ARM64
Your PCIe device is running on Windows x64. The next customer needs Linux support, while another deployment targets an ARM64 embedded platform.
The hardware interface remains the same, but a native driver implementation is tied to the operating system’s driver infrastructure. Supporting another OS can therefore introduce a separate implementation for resource mapping, DMA, interrupts, device lifecycle, and deployment.
For hardware expected to support multiple platforms, the architectural question is where to place that OS-specific layer and how much of the device-facing code can remain portable.
The cross-platform challenge
The device-specific logic stays largely the same across host OSes:
- Register access: register definitions, BAR offsets, initialization and control sequences
- DMA: engine programming, descriptor formats, ring management
- Interrupts: status, acknowledgment, and device-specific handling
- Application logic: device behavior and processing
What changes is the platform-specific integration.
A native driver connects this logic to the OS mechanisms for PCI resource management, memory mapping, DMA, interrupts, synchronization, and device lifecycle.
Supporting Windows, Linux, and macOS can therefore mean maintaining separate OS-specific implementations around the same device behavior.
Linux ARM/ARM64 can introduce another set of target-specific considerations, particularly on embedded systems using vendor BSPs, customized kernels, SoC-specific PCIe root complexes, and platform-specific DMA, IOMMU, or interrupt configurations.
Cross-platform does not mean platform-independent. The OS-specific work remains, but it does not have to define the device-specific implementation.
The WinDriver architecture
WinDriver provides the OS-specific driver infrastructure and exposes PCIe functionality through a common API across supported Windows, Linux, and macOS platforms.
The same API model covers core PCIe operations:
- BAR and MMIO access
- PCI configuration space
- DMA
- MSI and MSI-X
- Plug and Play events
The architecture keeps the device-specific implementation separate from the host OS:

The device-specific code remains portable across supported platforms. Native C/C++ applications are compiled for each target, while higher-level languages such as Python can be moved between platforms without recompiling the application itself.
As a result, adding another supported platform does not require implementing the PCIe access layer again against a different native driver framework.
Moving from Windows to Linux ARM
Consider a PCIe card with a device driver developed for Windows x64. BAR-mapped registers control the device, DMA handles high-throughput data transfers, and MSI-X signals transfer completion.
The same board now needs to run on an ARM64 Linux target.
The device-side implementation remains applicable:
- BAR layout and register offsets
- Initialization sequence
- DMA descriptor format
- DMA engine programming
- Interrupt status and acknowledgment logic
The target-specific work moves elsewhere.
The Linux ARM64 target still requires platform-specific build, deployment, and validation, particularly when working with custom kernels or vendor BSPs.
These platform requirements still need to be handled and tested, but the complete device-facing implementation does not need to be recreated for the new target.
Uriya Bartal, WinDriver R&D Team Lead, puts this into practical terms:

Hardware bring-up across platforms
DriverWizard provides a direct way to inspect and validate a PCIe device before starting work on your driver.
Developers can enumerate the device, inspect PCI configuration space and resources, access BAR-mapped registers, test interrupts, and generate an initial code base using the WinDriver API.
For an FPGA or custom PCIe board, the workflow can start with the hardware itself:

The same development model can then be used when bringing the device up on another supported platform.
This is particularly useful when hardware and software development run in parallel, while the FPGA register map, interrupt behavior, or DMA implementation is still evolving.
Kernel-level performance
Most PCIe device logic can start in user mode, where development and debugging are simpler than in kernel space.
WinDriver’s user-mode API supports register access, DMA setup, interrupt handling, and other device-control operations without requiring the device-specific implementation to run in the kernel.
For operations where interrupt latency, context-switch overhead, or execution timing becomes critical, WinDriver’s Kernel PlugIn provides an optional path for moving selected functionality into kernel space.eds efficient hardware access, but not a custom implementation of the entire operating-system driver stack.
Maintaining one PCIe codebase
Cross-platform requirements often appear after the first driver has already been developed.
🖥️ Windows x64 | Linux x64 | Linux ARM64 | custom BSP | macOS
A Windows x64 workstation may be the initial development target. A customer may later require Linux x64, an embedded deployment may introduce Linux ARM64 and a custom BSP, and another product environment may require macOS.
At this stage, each additional target becomes another implementation that has to stay aligned with the same hardware behavior.
The practical question is not whether each platform requires additional work. It does. The question is how much of that work belongs to the platform layer rather than the PCIe implementation.
Contact our team to discuss your cross-platform PCIe driver requirements.
Download WinDriver for free and start a 30-day trial.