PCIe驱动如何处理同一FPGA同一IRQ线的多个INTx中断?
Hey there, let's work through your problem together. I know you're dealing with an FPGA that uses Legacy INTx (since your team hasn't wrapped their heads around MSI/MSI-X yet), and you need to handle at least 8 distinct interrupts—originally using uio_pci_generic, but that setup only handles one interrupt out of the box. Here's how to make this work smoothly:
Legacy INTx only provides 4 possible lines (A/B/C/D) per PCI device, so you can't map 8 interrupts directly to separate lines. The fix involves aggregating those 8 signals on the FPGA side first, then adding logic for your software to decode which specific interrupt fired.
1. FPGA Hardware Tweaks (The Foundation)
You'll need to add a few key logic blocks to your FPGA design:
- Interrupt Status Register: A memory-mapped register (e.g., 32-bit) where each bit corresponds to one of your 8 interrupts. When an interrupt triggers, set its corresponding bit to 1. Ensure the bit stays set until the CPU writes to clear it.
- Interrupt Line Aggregation:
- Option 1 (Single INTx Line, Simplest): OR all 8 interrupt signals together and feed that into one INTx line. This works well if interrupts don't fire extremely frequently.
- Option 2 (Multiple INTx Lines, Better Performance): Group your 8 interrupts into up to 4 groups (since PCI allows 4 INTx lines), OR each group's signals, and map each group to a separate INTx line. This reduces interrupt storm risks and lets you narrow down the source faster.
- PCI Spec Compliance: Double-check if your INTx is level-triggered or edge-triggered (most Legacy INTx are level-triggered). For level-triggered, keep the signal high until the CPU acknowledges/clears the interrupt; for edge-triggered, send a short pulse.
2. Adapting uio_pci_generic for Multiple Interrupts
If you want to stick with uio_pci_generic, here's how to update your user-space code:
- Bind the Device: Confirm your FPGA is bound to
uio_pci_generic(uselspcito find the device ID, then echo it to/sys/bus/pci/drivers/uio_pci_generic/new_id). - Map MMIO Space: In your user-space program, open
/dev/uioX, usemmap()to map the FPGA's memory region (including your new status register) into user space. - Handle Interrupts: Use
poll()orread()on the/dev/uioXfile descriptor to wait for interrupts. When one fires:- Read the interrupt status register to see which bits are set.
- Run the corresponding handling logic for each set bit.
- Write to the status register to clear the bits (make sure your FPGA logic de-asserts the INTx signal when this happens).
- Note:
uio_pci_genericonly exposes one interrupt per device, so Option 1 (single INTx line) is easier to implement here. If you went with multiple INTx lines, a custom kernel driver would be a better fit.
3. Alternative: Write a Minimal Kernel Driver
If uio_pci_generic is too limiting (especially for performance-critical use cases or multiple INTx lines), a tiny custom kernel driver is worth considering:
- Register Interrupt Handlers: For each INTx line your FPGA uses, register a shared interrupt handler (since Legacy INTx lines are shared with other devices).
- Decode Interrupts in Kernel: In the handler, read the FPGA's status register to identify which specific interrupt(s) fired.
- Offload Work: Use
taskletorworkqueueto handle the actual processing (kernel interrupt handlers need to be as fast as possible). - User-Space Interface: Add a character device or sysfs nodes to let user-space know which interrupts fired, or handle logic entirely in the kernel if that makes sense.
- Key Tip: In the handler, return
IRQ_HANDLEDonly if you confirm the interrupt came from your FPGA; returnIRQ_NONEotherwise (since other devices might share the same INTx line).
4. Critical Pitfalls to Avoid
- Interrupt Storm Prevention: Ensure your status register captures all interrupts that fire in quick succession—don't let a new interrupt overwrite the status before the CPU reads it.
- Synchronization: If multiple threads handle interrupts in user space, use mutexes or other primitives to avoid race conditions when accessing the status register.
- Testing: Start with one interrupt, verify it works, then add more one by one. Use
cat /proc/interruptsto check if interrupts are being counted correctly.
Once you get this setup working, it'll handle your 8 interrupts reliably. And when your team is ready to move to MSI/MSI-X, you'll be able to map each interrupt to a unique vector directly, which simplifies things a lot—but this solution will get you up and running in the meantime.
内容的提问来源于stack exchange,提问作者mafzzz

