Brijesh-Thakkar opened a new issue, #20870:
URL: https://github.com/apache/nuttx/issues/20870

   <!--
   DRAFT, NOT POSTED.  Suggested labels: Area: Drivers, Board: x86_64, bug.
   Delete this comment before posting.
   -->
   
   **Title:** qemu-intel64:citest_smp (and jumbo): PCI UART1 can never attach; 
defconfig mismatch, and the PCI 16550 probe aborts at the first unconfigured 
port
   
   ## Description
   
   `qemu-intel64:citest_smp` and `qemu-intel64:jumbo` enable a PCI 16550 UART as
   `UART1`, but `/dev/ttyS1` is never registered, whatever QEMU PCI serial 
device
   is attached.  There are two independent causes:
   
   1. **The defconfigs are not satisfiable.**  They select the QEMU 
*single-port*
      card (`1b36:0002`) but do not set `CONFIG_16550_PCI_UART1_PORT`, whose
      Kconfig default for UART1 is `1`.  A single-port card has only port 0, so
      no card can match "device `0x0002`, port 1".
   2. **`pci_u16550_probe()` gives up at the first unconfigured port.**  The 
loop
      starts at port 0 and does `return -ENODEV` when no `UARTn` is configured
      for that port, so a configuration that only uses a later port of a
      multi-port card (for example only `UART1` = port 1 of a 2-port card)
      cannot work either.
   
   Nothing fails in CI because the CI QEMU command for `citest_smp` does not add
   any PCI serial device (`config.yaml`: `-m 2G -smp 4 -cpu max -nographic
   -serial mon:stdio`), so the PCI UART is simply never exercised.  I found this
   while testing an unrelated change.
   
   ## Environment
   
   - master at `d0886af95d`, x86_64 host, QEMU 10.2.1 with KVM, GCC 15.2.
   - Reproduced on master (no local changes).
   
   ## Configuration (as shipped)
   
   `boards/x86_64/qemu/qemu-intel64/configs/citest_smp/defconfig` (the same
   `PCI_UART1` lines are in `jumbo/defconfig`):
   
   ```
   CONFIG_16550_PCI_UART=y
   CONFIG_16550_PCI_UART1=y
   CONFIG_16550_PCI_UART1_VENDOR=0x1b36
   CONFIG_16550_PCI_UART1_DEVICE=0x0002
   CONFIG_16550_PCI_UART1_CLOCK=1843200
   CONFIG_16550_UART0=y
   CONFIG_16550_UART0_SERIAL_CONSOLE=y
   CONFIG_16550_UART0_BASE=0x3f8
   CONFIG_16550_UART0_IRQ=36
   ```
   
   There is no `CONFIG_16550_PCI_UART1_PORT` line, so the Kconfig default 
applies
   (`drivers/serial/Kconfig-pci:116-121`):
   
   ```
   116  config 16550_PCI_UART1_PORT
   117          int "16550 UART1 PCI port"
   118          default 1
   119          ---help---
   120                  Port in multi-port device. Starts from 0.
   121                  Must be set to 0 for single port devices .
   ```
   
   So the resulting `.config` has `CONFIG_16550_PCI_UART1_PORT=1`.
   
   ## Steps to reproduce
   
   ```
   ./tools/configure.sh qemu-intel64:citest_smp
   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
   ```
   
   (`citest_smp` has `CONFIG_ARCH_PVHBOOT=y`, so `-kernel` works.)
   
   ## Expected
   
   `UART1` is the PCI UART, so `/dev/ttyS1` exists:
   
   ```
   /dev:
    console
    null
    pci
    random
    telnet
    ttyS0
    ttyS1
    zero
   ```
   
   ## Actual
   
   ```
   /dev:
    console
    null
    pci
    random
    telnet
    ttyS0
    zero
   ```
   
   The kernel log shows that the driver probes the device but never registers a
   port: there are `pci_u16550_probe: Enabled bus mastering` and `Enabled memory
   resources` lines, and no `pci_u16550_register: Register /dev/ttyS1`.  The
   result is identical with `-device pci-serial`, `pci-serial-2x` and
   `pci-serial-4x`.
   
   ## Analysis
   
   `drivers/serial/uart_pci_16550.c` at `d0886af95d`:
   
   ```
   698    /* Get available ports for this device */
   699
   700    ports = type->ports * (PCI_FUNC(dev->devfn) + 1);
   701    port  = type->ports * PCI_FUNC(dev->devfn);
   702
   703    /* Register all ports for this device */
   704
   705    for (; port < ports; port++)
   706      {
   707        /* Get port address */
   708
   709        base += type->portincr * port;
   ...
   714        for (i = 0; i < nitems(g_pci_u16550_dev); i++)
   715          {
   ...
   719            if ((priv->vendor == PCI_ANY_ID ||
   720                 priv->vendor == dev->vendor) &&
   721                (priv->device == PCI_ANY_ID ||
   722                 priv->device == dev->device) &&
   723                priv->port == port)
   724              {
   725                break;
   726              }
   ...
   732        /* Not found */
   733
   734        if (udev == NULL)
   735          {
   736            return -ENODEV;
   737          }
   ```
   
   - Cause 1: the QEMU device ids in `g_pci_u16550_id_table` are `1b36:0002`
     (1 port), `1b36:0003` (2 ports) and `1b36:0004` (4 ports).  With
     `DEVICE=0x0002` the card has one port (port 0), but `UART1` asks for port 
1,
     so line 723 never matches (`pci-serial-2x`/`4x` do not match at all,
     because their device ids are `0x0003`/`0x0004`).
   - Cause 2: when no instance is configured for the current port,
     line 736 returns from the whole probe instead of moving to the next port, 
so
     a configuration that has no `UART0` but has `UART1` (port 1) never reaches
     port 1, even on a card that has it.
   - Related, not needed for this report: line 709 accumulates (`base +=`)
     instead of computing the port address from the BAR base, so for a card with
     `portincr != 0` and 3 or more ports the third and fourth ports get offsets
     24 and 48 instead of 16 and 24 (with `portincr = 8`).  I only checked this
     by reading the code; I did not test it on a 4-port card.
   
   ## What I verified
   
   All builds are `citest_smp` with only the stated change; "probe fix" is the
   patch below.  `ttyS1` = `/dev/ttyS1` was registered.
   
   | Build | `UART1` config | QEMU device | `ttyS1` |
   |---|---|---|---|
   | master | as shipped (`0x0002`, port 1 by default) | `pci-serial` | no |
   | master | as shipped | `pci-serial-2x` | no |
   | master | as shipped | `pci-serial-4x` | no |
   | master | `PORT=0` added (`0x0002`) | `pci-serial` | **yes** |
   | master | `DEVICE=0x0003`, `PORT=1` | `pci-serial-2x` | no |
   | master + probe fix | as shipped | `pci-serial`, `-2x`, `-4x` | no |
   | master + probe fix | `DEVICE=0x0003`, `PORT=1` | `pci-serial-2x` | **yes** 
|
   
   So cause 1 alone explains the `citest_smp`/`jumbo` symptom, and cause 2 is a
   separate bug that only shows up for a card whose first port is not 
configured.
   
   ## Proposed fix
   
   1. **Defconfigs** (`citest_smp`, `jumbo`): add
      `CONFIG_16550_PCI_UART1_PORT=0`, which matches the single-port
      `1b36:0002` card they already name.  Optionally also add
      `-device pci-serial` to the CI `exec_args` so the port is actually tested.
   2. **Driver**: do not abort the probe at the first unconfigured port, and
      compute each port address from the BAR base:
   
   ```diff
   @@ pci_u16550_probe()
      uintptr_t                           base = 0;
   +  uintptr_t                           portbase;
   +  unsigned int                        registered = 0;
   @@
   -      base += type->portincr * port;
   +      portbase = base + type->portincr * port;
   @@
   -      /* Not found */
   +      /* Not configured: skip this port, others may still be configured */
   
          if (udev == NULL)
            {
   -          return -ENODEV;
   +          continue;
            }
   @@
   -      ret = pci_u16550_initialize(priv, type, base, dev, mmio);
   +      ret = pci_u16550_initialize(priv, type, portbase, dev, mmio);
   @@
          ret = pci_u16550_register(udev);
          if (ret < 0)
            {
              return ret;
            }
   +
   +      registered++;
        }
   
   -  return OK;
   +  /* Fail only if no port of this device is configured */
   +
   +  return registered > 0 ? OK : -ENODEV;
   ```
   
   This keeps `-ENODEV` for a device none of whose ports is configured.  It 
builds
   and passes `tools/checkpatch.sh`; I booted it only on QEMU (table above).  I
   have not tested it on an AX99100 card.
   
   I can send a PR for the defconfig part, the driver part, or both, whichever
   the maintainers prefer.
   


-- 
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]

Reply via email to