Brijesh-Thakkar opened a new issue, #20921:
URL: https://github.com/apache/nuttx/issues/20921
<!--
DRAFT, NOT POSTED. Suggested labels: Area: Drivers, Board: x86_64, bug.
Delete this comment before posting.
-->
**Title:** qemu-intel64: opening a PCI 16550 UART on a shared legacy IRQ
asserts in up_enable_irq() without CONFIG_IRQCHAIN
## Description
On `qemu-intel64`, opening a PCI 16550 UART that uses a legacy (INTx)
interrupt
asserts in `up_enable_irq()` (`arch/x86_64/src/intel64/intel64_irq.c:785`) if
`CONFIG_IRQCHAIN` is not set. The cause is that QEMU's default e1000 NIC and
the PCI serial card are routed to the same legacy IRQ (43), the NIC enables
it
first, and the x86_64 code treats a second `up_enable_irq()` on the same line
as an error unless IRQ chains are enabled.
`qemu-intel64:citest_smp` and `jumbo` do not set `CONFIG_IRQCHAIN`, so they
are
affected as soon as their PCI UART1 registers (see #20870 / #20918 / #20919).
CI does not hit it today because no PCI serial device is attached to its QEMU
command (as noted in #20870); adding `-device pci-serial` there, as #20870
suggests optionally, would expose it.
## Environment
- upstream/master `a69dacd613`, apps `e0174bf71`
- Host: Ubuntu 26.04, Linux 7.0.0-38, x86_64, KVM
- QEMU 10.2.1 (Debian 1:10.2.1+ds-1ubuntu3.2), GCC 15.2.0 (Ubuntu
15.2.0-16ubuntu1)
## Steps to reproduce (unmodified master + one local config line)
`ttyS1` only registers on master with `CONFIG_16550_PCI_UART1_PORT=0` (the
defconfig fix in #20918); I set it locally so the port exists:
```
./tools/configure.sh qemu-intel64:citest_smp
kconfig-tweak --file .config --set-val 16550_PCI_UART1_PORT 0
make olddefconfig && make -j
qemu-system-x86_64 -cpu host -enable-kvm -m 2G -smp 4 -kernel nuttx \
-nographic -serial mon:stdio \
-chardev null,id=c1 -device pci-serial,chardev=c1
nsh> ls /dev # ttyS1 is listed
nsh> echo hi > /dev/ttyS1
```
The QEMU default NIC (e1000, `00:03.0`) is present; `pci-serial` lands on the
next slot, `00:04.0`.
## Actual
```
nsh> echo hi > /dev/ttyS1
[CPU0] dump_assert_info: Current Version: NuttX 13.1.0 a69dacd613 Oct 23
2026 13:14:52 x86_64
[CPU0] dump_assert_info: Assertion failed : at file: :0 task(CPU0): nsh_main
process: nsh_main 0x10093faf4
[CPU0] up_dump_register: ----------------CUT HERE-----------------
...
```
The console message has no file/line (`CONFIG_DEBUG_ASSERTIONS` is off).
Backtrace from gdb (`hbreak _assert`):
```
#2 up_enable_irq (irq=43) at intel64/intel64_irq.c:785
#3 u16550_attach (dev=<g_pci_u16550_dev1>) at serial/uart_16550.c:996
#4 uart_open (filep=...) at serial/serial.c:884
#5 file_vopen (... path="/dev/ttyS1", oflags=577 ...) at vfs/fs_open.c:256
#6 nx_vopen (...) at vfs/fs_open.c:327
#7 open (path="/dev/ttyS1", oflags=577) at vfs/fs_open.c:486
#8 nsh_execute (...) at nsh_parse.c:675
```
## Expected
The port opens and works, as it does when `CONFIG_IRQCHAIN=y` (below),
without
breaking the other device on the same line.
## Analysis
What I verified by running (QEMU + gdb, unmodified master, config above):
1. IRQ 43 is the legacy line of both devices. A hardware watchpoint on
`g_irq_priv[43].busy` from reset shows the only write before the failing
open is `up_enable_irq(43)` called from `e1000_enable()`
(`drivers/net/e1000.c:1377`,
via `netdev_ifup`), so the line is already `busy` when `ttyS1` is opened.
2. The `open()` is the first `up_enable_irq(43)` from the serial driver
and it enters with `busy=1, msi=0`, hence the assert.
3. With `-nic none` (no e1000) the same `echo hi > /dev/ttyS1` succeeds on
the
same build, without `CONFIG_IRQCHAIN`.
4. With `CONFIG_IRQCHAIN=y` and the NIC present the `open()` succeeds, and
the
port works (TX and RX checked against a QEMU socket chardev).
5. The collision depends on the PCI slot: `-device pci-serial,addr=0x4` and
`0x7` assert, `0x5` and `0x6` open fine, i.e. QEMU shares INTx lines
between
slots.
What I only read from the code:
- `up_enable_irq()` (`intel64_irq.c:765`) returns early for MSI, otherwise,
`#ifndef CONFIG_IRQCHAIN`, does `ASSERT(0)` if `g_irq_priv[irq].busy`
(lines 780-787). `busy` is a bool, set at line 798 on the first enable and
cleared by `up_disable_irq()` (line 750); it is not a count. The assert is
the guard for the non-chain case: `irq_attach()` (`sched/irq/irq_attach.c`)
without `CONFIG_IRQCHAIN` just overwrites `g_irqvector[ndx].handler`, so a
second driver on the line would silently replace the first one's ISR.
With `CONFIG_IRQCHAIN` it goes through `irqchain_attach()` instead
(`#ifdef CONFIG_IRQCHAIN` in `irq_attach()`), and `up_enable_irq()` skips
the assert and the
unmask.
- `pci_get_irq()` -> `x86_64_pci_get_irq()` -> `up_get_legacy_irq()`
(`intel64_irq.c:853`) returns `IRQ0 + PCI_INTERRUPT_LINE`, with no sharing
logic. `uart_pci_16550.c` tries MSI first (`pci_alloc_irq`, l.576) and
falls back to the legacy line (l.591-595); QEMU's `pci-serial` has no MSI,
as far as I know, which is why IRQ 43 shows up. I did not check the MSI
failure path separately.
- `nxterm` is the only qemu-intel64 defconfig with `CONFIG_IRQCHAIN=y`.
### Related: with CONFIG_IRQCHAIN=y, closing the port breaks the NIC
(verified)
`IRQCHAIN` avoids the assert but the shared line is still handled as
one-owner
on the disable side. `u16550_detach()` calls `up_disable_irq(priv->irq)`
first
(`uart_16550.c:1031`) and only then `irqchain_detach()` (l.1032), so on close
the watchpoint shows `up_disable_irq(43)` from `uart_close` clearing `busy`
and
masking the IOAPIC pin (`intel64_irq.c:747-750`) while the e1000 handler is
still on the line.
Test (`CONFIG_IRQCHAIN=y`, QEMU `-nic
user,model=e1000,hostfwd=tcp:127.0.0.1:2323-10.0.1.2:23`,
telnet to the guest's `telnetd` from the host): a connection gets the NSH
banner before `echo hi > /dev/ttyS1`, and receives 0 bytes after it (2 runs).
Control: the same two connections without touching `ttyS1` both get the
banner. So `IRQCHAIN` alone fixes the assert but trades it for a lost NIC
interrupt after the first close of the serial port.
## Possible fixes (no patch)
1. **Make the x86_64 enable/disable reference counted.** Replace `busy`
with a
per-IRQ count: unmask the IOAPIC pin on 0->1, mask on 1->0, and drop the
non-chain assert in favor of requiring `IRQCHAIN` only where a second
`irq_attach()` would replace a handler. Fixes both the assert and the
close problem for any shared legacy line (also the next device that
shares). Cost: touches arch code and needs a decision on non-chain shared
lines (still unsafe, `irq_attach` overwrites).
2. **Select `IRQCHAIN` for PCI legacy IRQs on x86_64** (e.g. `select
IRQCHAIN`
under `ARCH_X86_64` + `PCI`, or add `CONFIG_IRQCHAIN=y` to
`citest_smp`/`jumbo`). Small and local, and it fixes the assert, but
alone
it leaves the close problem above (the serial detach masks the shared
line), so it should be combined with 1 or with a change in
`u16550_detach()` to not disable a line that still has other handlers.
Costs the pre-allocated chain entries (`CONFIG_PREALLOC_IRQCHAIN`,
default 8).
3. **Avoid the shared line**: prefer MSI/MSI-X where the device has it (real
cards such as AX99100 may) and, for QEMU, place `pci-serial` on a slot
that
does not share the e1000's PIRQ (`addr=0x5`/`0x6` above) or use `-nic
none`.
No code change, but it is a workaround: `pci-serial` has no MSI and the
default slot collides.
I'd lean towards 1 (it fixes the cause), but would like the maintainers' view
before sending anything, as it changes `up_enable_irq()`/`up_disable_irq()`
semantics for all x86_64 users.
## Related
#20870 (PCI UART1 never registers), #20918 (defconfig fix), #20919 (probe
fix).
Neither PR changes this; this issue only shows up once `ttyS1` is registered
and opened.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]