From 64a413ad077f1521f77400ad80a35e8beac758ea Mon Sep 17 00:00:00 2001 From: Chaitanya Vadrevu Date: Thu, 20 Aug 2026 15:11:47 -0500 Subject: [PATCH 1/4] ARM: dts: ni-zynq: Restore fpgaperipheral node The fpgaperipheral driver only binds to devices with the "fpgaperipheral" compatible. Replacing its node with the Zynq devcfg node prevents the driver from probing and breaks FPGA reconfiguration notifications for dependent peripherals. Restore the fpgaperipheral node and disable the inherited devcfg and FPGA region nodes so that only one driver can own the device configuration register range. Fixes: 74561fd411d3 ("ARM: dts: ni-zynq: Drop obsolete bindings") Assisted-by: Copilot:GPT-5.6-Sol Signed-off-by: Chaitanya Vadrevu --- arch/arm/boot/dts/xilinx/ni-zynq.dtsi | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/arch/arm/boot/dts/xilinx/ni-zynq.dtsi b/arch/arm/boot/dts/xilinx/ni-zynq.dtsi index f8a4850c093e1..07fabec8f068a 100644 --- a/arch/arm/boot/dts/xilinx/ni-zynq.dtsi +++ b/arch/arm/boot/dts/xilinx/ni-zynq.dtsi @@ -105,6 +105,11 @@ clock-frequency = <58824000>; }; + fpgaperipheral@f8007000 { + compatible = "fpgaperipheral"; + reg = <0xf8007000 0x100>; + }; + i2c2: i2c@81000000 { compatible = "xlnx,xps-iic-2.00.a"; status = "disabled"; @@ -173,6 +178,14 @@ arm,standby-mode = <0>; }; +&devcfg { + status = "disabled"; +}; + +&fpga_full { + status = "disabled"; +}; + /* * The CoreSight debug/trace macrocells are not accessible on NI Zynq-based * targets: reading their AMBA peripheral-ID registers (done unconditionally From 5b22cf770ad096543eb9fec43bc30a55b2b0c2d4 Mon Sep 17 00:00:00 2001 From: Chaitanya Vadrevu Date: Thu, 20 Aug 2026 20:15:37 -0500 Subject: [PATCH 2/4] irqchip/gic: Add fixed IRQ mappings for NI Zynq Some NI Zynq drivers depend on Linux IRQ numbers matching the corresponding GIC hardware interrupt numbers. The dynamically allocated GIC domain does not preserve that historical numbering contract. Add an NI Zynq-specific option that creates a legacy domain for the primary GIC and pre-associates GIC interrupts starting at 16 with the same Linux IRQ numbers. Fail initialization if the required descriptor range cannot be reserved or the identity mappings cannot be established. Retain the GIC firmware node and existing translation callback so device-tree interrupt specifications continue to work. Also retain the allocation and free callbacks required for current SMP SGI setup. Secondary GICs continue using the normal linear domain. Enable the option in nati_zynq_defconfig. Assisted-by: Copilot:GPT-5.6-Sol Signed-off-by: Chaitanya Vadrevu --- arch/arm/configs/nati_zynq_defconfig | 1 + arch/arm/mach-zynq/Kconfig | 10 ++++ drivers/irqchip/irq-gic.c | 71 ++++++++++++++++++++++++++-- 3 files changed, 79 insertions(+), 3 deletions(-) diff --git a/arch/arm/configs/nati_zynq_defconfig b/arch/arm/configs/nati_zynq_defconfig index 8b849b1760254..ad74851ef7245 100644 --- a/arch/arm/configs/nati_zynq_defconfig +++ b/arch/arm/configs/nati_zynq_defconfig @@ -23,6 +23,7 @@ CONFIG_EXPERT=y CONFIG_PERF_EVENTS=y CONFIG_ARCH_VEXPRESS=y CONFIG_ARCH_ZYNQ=y +CONFIG_NI_ZYNQ_GIC_LEGACY_IRQDOMAIN=y CONFIG_ARM_ERRATA_754322=y CONFIG_ARM_ERRATA_764369=y CONFIG_ARM_ERRATA_775420=y diff --git a/arch/arm/mach-zynq/Kconfig b/arch/arm/mach-zynq/Kconfig index 05be5aa9b4028..6335678a1fd28 100644 --- a/arch/arm/mach-zynq/Kconfig +++ b/arch/arm/mach-zynq/Kconfig @@ -15,3 +15,13 @@ config ARCH_ZYNQ select SOC_BUS help Support for Xilinx Zynq ARM Cortex A9 Platform + +config NI_ZYNQ_GIC_LEGACY_IRQDOMAIN + bool "Use fixed GIC IRQ numbers on NI Zynq targets" + depends on ARCH_ZYNQ + help + Create fixed Linux IRQ mappings for the primary GIC so that its + Linux IRQ numbers equal the corresponding GIC hardware IRQ numbers. + This preserves compatibility with NI drivers that use fixed IRQs. + Enable this only on NI Zynq systems that require the legacy IRQ + numbering contract. diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index 1269ab8eb726a..86a5001a6fbb1 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -1160,6 +1160,73 @@ static const struct irq_domain_ops gic_irq_domain_hierarchy_ops = { .free = irq_domain_free_irqs_top, }; +static const struct irq_domain_ops gic_irq_domain_legacy_ops = { + .map = gic_irq_domain_map, + .translate = gic_irq_domain_translate, + .alloc = gic_irq_domain_alloc, + .free = irq_domain_free_irqs_top, +}; + +static struct irq_domain *gic_create_irq_domain(struct gic_chip_data *gic, + struct fwnode_handle *handle, + unsigned int gic_irqs) +{ + struct irq_domain *domain; + unsigned int irq_count; + unsigned int irq; + int irq_base; + + if (!IS_ENABLED(CONFIG_NI_ZYNQ_GIC_LEGACY_IRQDOMAIN) || + gic != &gic_data[0]) + return irq_domain_create_linear(handle, gic_irqs, + &gic_irq_domain_hierarchy_ops, + gic); + + if (gic_irqs <= NR_IRQS_LEGACY) + return NULL; + + irq_count = gic_irqs - NR_IRQS_LEGACY; + irq_base = irq_alloc_descs(NR_IRQS_LEGACY, NR_IRQS_LEGACY, + irq_count, numa_node_id()); + if (irq_base != NR_IRQS_LEGACY) { + if (irq_base >= 0) + irq_free_descs(irq_base, irq_count); + pr_err("GIC: cannot reserve fixed IRQ range %u-%u (%d)\n", + NR_IRQS_LEGACY, gic_irqs - 1, irq_base); + return NULL; + } + + domain = irq_domain_create_legacy(handle, irq_count, NR_IRQS_LEGACY, + NR_IRQS_LEGACY, + &gic_irq_domain_legacy_ops, gic); + if (!domain) { + irq_free_descs(irq_base, irq_count); + return NULL; + } + + for (irq = NR_IRQS_LEGACY; irq < gic_irqs; irq++) { + if (irq_find_mapping(domain, irq) != irq) { + unsigned int mapped_irq; + + pr_err("GIC: failed to establish fixed mapping for IRQ%u\n", + irq); + for (mapped_irq = NR_IRQS_LEGACY; + mapped_irq < gic_irqs; mapped_irq++) { + if (irq_find_mapping(domain, mapped_irq) == mapped_irq) + irq_dispose_mapping(mapped_irq); + else + irq_free_desc(mapped_irq); + } + irq_domain_remove(domain); + return NULL; + } + } + + pr_info("GIC: using fixed Linux IRQ mapping %u-%u\n", + NR_IRQS_LEGACY, gic_irqs - 1); + return domain; +} + static int gic_init_bases(struct gic_chip_data *gic, struct fwnode_handle *handle) { @@ -1207,9 +1274,7 @@ static int gic_init_bases(struct gic_chip_data *gic, gic_irqs = 1020; gic->gic_irqs = gic_irqs; - gic->domain = irq_domain_create_linear(handle, gic_irqs, - &gic_irq_domain_hierarchy_ops, - gic); + gic->domain = gic_create_irq_domain(gic, handle, gic_irqs); if (WARN_ON(!gic->domain)) { ret = -ENODEV; goto error; From 466205a3909f6929e9598c3e9a75d2f57db5d0d8 Mon Sep 17 00:00:00 2001 From: Chaitanya Vadrevu Date: Fri, 21 Aug 2026 00:01:47 -0500 Subject: [PATCH 3/4] net: macb: Avoid PHY access after disconnect during FPGA reload macb_close() disconnects the PHY through phylink and clears net_device::phydev. The FPGA notifier subsequently accesses phydev to stop the PHY state machine and release its interrupt, causing a NULL pointer dereference when FPGA programming begins. The FPGA-up path similarly accesses phydev before macb_open() reconnects it. Remove the redundant direct PHY operations from both paths. macb_close() and macb_open() already manage the PHY lifecycle through phylink. Assisted-by: Copilot:GPT-5.6-Sol Signed-off-by: Chaitanya Vadrevu --- drivers/net/ethernet/cadence/macb_main.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c index bb7f9e73ca2de..426559be1cf3a 100644 --- a/drivers/net/ethernet/cadence/macb_main.c +++ b/drivers/net/ethernet/cadence/macb_main.c @@ -4487,7 +4487,6 @@ static const struct net_device_ops macb_netdev_ops = { static int macb_fpga_notifier(struct notifier_block *nb, unsigned long val, void *data) { struct macb *bp = container_of(nb, struct macb, fpga_notifier); - struct net_device *dev = bp->dev; switch (val) { case FPGA_PERIPHERAL_DOWN: @@ -4501,9 +4500,6 @@ static int macb_fpga_notifier(struct notifier_block *nb, unsigned long val, void if (netif_running(bp->dev)) { dev_deactivate(bp->dev); macb_close(bp->dev); - if (phy_interrupt_is_valid(dev->phydev)) - phy_free_interrupt(dev->phydev); - phy_stop_machine_nolink(dev->phydev); } bp->fpga_down = 1; @@ -4524,9 +4520,6 @@ static int macb_fpga_notifier(struct notifier_block *nb, unsigned long val, void /* If the interface has been opened. */ if (netif_running(bp->dev)) { - phy_start_machine(dev->phydev); - if (phy_interrupt_is_valid(dev->phydev)) - phy_request_interrupt(dev->phydev); macb_open(bp->dev); dev_activate(bp->dev); } From 8895496c5743010ae3376b39266482c8b7e9066d Mon Sep 17 00:00:00 2001 From: Chaitanya Vadrevu Date: Fri, 21 Aug 2026 13:28:33 -0500 Subject: [PATCH 4/4] serial: 8250_ni: handle FPGA reprogramming Suspend device-tree NI 16550 UARTs before FPGA programming and resume them after successful programming. This reinitializes FPGA-backed UARTs, including the serial console, after the FPGA fabric is reconfigured. After suspending a port, replace its register accessors with no-op callbacks to prevent concurrent console or TTY operations from accessing unavailable FPGA MMIO and locking up the system. Restore the normal accessors before resuming the port. Leave ports suspended when programming fails and unregister the FPGA notifier when removing the device. Signed-off-by: Jaeden Amero Signed-off-by: Xander Huff Signed-off-by: Brad Mouring Signed-off-by: Gratian Crisan [cvadrevu: reworked from commit dcf0b05acb5d ("8250: Handle FPGA reprogramming")] Assisted-by: Copilot:GPT-5.6-Sol Signed-off-by: Chaitanya Vadrevu --- drivers/tty/serial/8250/8250_ni.c | 74 +++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/drivers/tty/serial/8250/8250_ni.c b/drivers/tty/serial/8250/8250_ni.c index e9975dfb93d3c..988e0bff6dc33 100644 --- a/drivers/tty/serial/8250/8250_ni.c +++ b/drivers/tty/serial/8250/8250_ni.c @@ -18,12 +18,17 @@ #include #include #include +#include #include #include #include #include #include +#ifdef CONFIG_FPGA_PERIPHERAL +#include +#endif + #include "8250.h" /* Extra bits in UART_ACR */ @@ -77,8 +82,58 @@ struct ni16550_device_info { struct ni16550_data { int line; struct clk *clk; +#ifdef CONFIG_FPGA_PERIPHERAL + struct notifier_block fpga_notifier; + bool fpga_notifier_registered; + bool fpga_suspended; +#endif }; +#ifdef CONFIG_FPGA_PERIPHERAL +static u32 ni16550_disabled_serial_in(struct uart_port *port, + unsigned int offset) +{ + return 0; +} + +static void ni16550_disabled_serial_out(struct uart_port *port, + unsigned int offset, u32 value) +{ +} + +static int ni16550_fpga_notify(struct notifier_block *nb, + unsigned long event, void *unused) +{ + struct ni16550_data *data = container_of(nb, struct ni16550_data, + fpga_notifier); + struct uart_8250_port *uart = serial8250_get_port(data->line); + + switch (event) { + case FPGA_PERIPHERAL_DOWN: + if (!data->fpga_suspended) { + serial8250_suspend_port(data->line); + uart->port.serial_in = ni16550_disabled_serial_in; + uart->port.serial_out = ni16550_disabled_serial_out; + data->fpga_suspended = true; + } + break; + case FPGA_PERIPHERAL_UP: + if (data->fpga_suspended) { + serial8250_set_defaults(uart); + serial8250_resume_port(data->line); + data->fpga_suspended = false; + } + break; + case FPGA_PERIPHERAL_FAILED: + break; + default: + return NOTIFY_DONE; + } + + return NOTIFY_OK; +} +#endif + static int ni16550_enable_transceivers(struct uart_port *port) { u8 pcr; @@ -405,6 +460,20 @@ static int ni16550_probe(struct platform_device *pdev) return ret; data->line = ret; +#ifdef CONFIG_FPGA_PERIPHERAL + if (dev_of_node(dev)) { + data->fpga_notifier.notifier_call = ni16550_fpga_notify; + ret = blocking_notifier_chain_register(&fpgaperipheral_notifier_list, + &data->fpga_notifier); + if (ret) { + serial8250_unregister_port(data->line); + return dev_err_probe(dev, ret, + "failed to register FPGA notifier\n"); + } + data->fpga_notifier_registered = true; + } +#endif + platform_set_drvdata(pdev, data); return 0; } @@ -413,6 +482,11 @@ static void ni16550_remove(struct platform_device *pdev) { struct ni16550_data *data = platform_get_drvdata(pdev); +#ifdef CONFIG_FPGA_PERIPHERAL + if (data->fpga_notifier_registered) + blocking_notifier_chain_unregister(&fpgaperipheral_notifier_list, + &data->fpga_notifier); +#endif serial8250_unregister_port(data->line); }