Skip to content

serial: skip 8250 ports the firmware advertises but does not populate - #8

Open
europaul wants to merge 2 commits into
mainfrom
serial-skip-unpopulated-8250-ports
Open

serial: skip 8250 ports the firmware advertises but does not populate#8
europaul wants to merge 2 commits into
mainfrom
serial-skip-unpopulated-8250-ports

Conversation

@europaul

Copy link
Copy Markdown

Problem

Hardware inventory reported serial ports that do not exist. On an x86 machine whose firmware advertises 32 8250 ports but populates only 4, all 32 were listed, and each of the 28 unpopulated ones carried a fabricated "ioport_range": "0000-0007".

Two independent defects in pkg/serial/serial_linux.go combined to produce that:

  • serialPortFromTTY() treated a tty as a serial port whenever /sys/class/tty/<tty>/device existed. The kernel creates that symlink for every 8250 port the firmware advertises, populated or not, so unpopulated ports passed the check just like real ones.
  • readUintHex() compared the raw attribute text against "0" before stripping the 0x prefix, so "0x0" slipped past the guard and parsed as a valid base address. The I/O range was then computed as 0 .. 0+7, which is where 0000-0007 came from.

Fix

Reject a zero value in readUintHex() by stripping the prefix first, and filter on the value of the port attribute in serialPortFromTTY(). The serial core exposes port for every uart_port it manages, and an unpopulated port reads back zero because it has neither an I/O base nor a mapped address.

The subtlety is that absence of the attribute has to keep meaning something different. A UART behind a USB-to-serial converter (ttyUSB*, ttyACM*) is driven by usbserial rather than the serial core and exposes no port and no irq at all — those ports are real and were already handled correctly. A blanket "require a non-zero port" would have silently dropped every USB-attached serial port. So the discriminator is the attribute being present and zero, not merely reading as zero.

type and io_type look like they could serve here and cannot: a populated ttyS1 at port=0x2F8 reports type=0, exactly like an unpopulated port.

Attribute sets observed on a 6.12 kernel:

tty attributes present values
ttyUSB0 (FTDI converter) dev device power subsystem uevent no port, no irq, no io_type
ttyS0 (populated 8250) ... irq port io_type type line ... port=0x3F8 irq=4
ttyS4 (advertised, unpopulated) same as ttyS0 port=0x0 irq=0

Tests

TestSerialUnpopulatedPorts is table-driven over synthetic sysfs trees built under t.TempDir() and read through New(option.WithChroot(root)), following the existing TestSerialPCIParentBehindBridge pattern — no root privileges or real hardware needed. It covers a populated 8250, an unpopulated one (port=0x0), a USB UART with a USB device ancestor, and virtual ttys with no device symlink.

The USB case passes both before and after this change; it is there as a regression guard for the constraint above.

Note on COM numbering

COM%d names renumber once the phantoms are dropped, since the counter only advances for ports that are actually reported. A tree of ttyS0, ttyS1, [3 unpopulated], ttyS5, ttyUSB0 now yields COM1..COM4 with no gaps, and one table case pins that. This is the intended outcome rather than a regression, but it is a visible change for any consumer that keyed on the old numbering.


Generated by Claude Code

readUintHex compared the raw attribute text against "0" before stripping
the "0x" prefix the kernel emits, so the string "0x0" slipped past the
guard, parsed as a valid base address and was reported as usable.

An 8250 port the firmware advertises but the board does not populate has
neither an I/O base nor a mapped address, and its port attribute reads
back exactly that value. Every such port therefore ended up carrying a
fabricated 0000-0007 I/O range, computed as 0 through 0+7.

Strip the prefix first and reject a zero value after parsing, which also
covers the other spellings of zero the guard used to miss.

Signed-off-by: Paul Gaiduk <paulg@zededa.com>
A tty was accepted as a serial port whenever /sys/class/tty/<tty>/device
existed. The kernel creates that symlink for every 8250 port the firmware
advertises, populated or not, so a machine whose firmware advertises 32
ports while wiring up 4 reported all 32.

Filter on the value of the "port" attribute instead. The serial core
exposes it for every uart_port it manages, and an unpopulated port reads
back zero because it has no I/O base and no mapped address.

Absence of the attribute has to keep meaning something different. A UART
behind a USB-to-serial converter is driven by usbserial rather than the
serial core and exposes neither "port" nor "irq"; those ports are real and
were already reported correctly. So the discriminator is the attribute
being present and zero, not merely a zero value. Neither "type" nor
"io_type" can stand in here - a populated ttyS1 at 0x2f8 reports type 0
just as an unpopulated port does.

Add table-driven cases over synthetic sysfs trees covering a populated
8250, an unpopulated one, a USB-attached UART with a USB device ancestor,
and a virtual tty with no device symlink. The attribute sets are taken
from a 6.12 kernel. One case pins the COM numbering closing up over the
skipped ports rather than leaving gaps, which is the intended result.

Signed-off-by: Paul Gaiduk <paulg@zededa.com>
@europaul

Copy link
Copy Markdown
Author

@christoph-zededa do you think such a change would make sense? or is it better to keep all the unpopulated serial devices like the kernel does?

@christoph-zededa

Copy link
Copy Markdown

@christoph-zededa do you think such a change would make sense? or is it better to keep all the unpopulated serial devices like the kernel does?

lgtm, but perhaps you do the PR directly upstream?

@europaul

Copy link
Copy Markdown
Author

@christoph-zededa now I think it's probably not such a good idea if ghw shows less devices than the kernel in /dev. Also this might change the numbering of the COM ports for already existing EVE devices that use model-less onboarding.

@christoph-zededa

Copy link
Copy Markdown

@christoph-zededa now I think it's probably not such a good idea if ghw shows less devices than the kernel in /dev.

I don't think it is a problem if ghw shows less devices than in /dev.

Also this might change the numbering of the COM ports for already existing EVE devices that use model-less onboarding.

You're right, I see, they do:

for i, serialDev := range serialDevices {
    label := fmt.Sprintf("COM%d", i+1)

in zedcloud.

This might have consequences when updating eve vs. directly installing the new version of eve on the same hardware model ...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants