Re-ordering serial ports on a multiport card

rhelserial portttyudev

I have a Red Hat Enterprise 6.2 machine with two on-board serial ports and a PCIe card with 8 additional serial ports (16C950 UARTs, 16C550 compliant). I've added the kernel option 8250.nr_uarts=10 so that all devices show up under /dev.

The two on-board devices show up as ttyS0 and ttyS1, as expected, but the serial ports on the PCIe card aren't being ordered by their I/O port as I would expect them to. Otherwise the devices work fine, it's just that it's not very elegant that the tty order doesn't match the order on the breakout cable for the board. Any ideas on how to change the order?

Output form setserial:

# setserial -g /dev/ttyS*
/dev/ttyS0, UART: 16550A, Port: 0x03f8, IRQ: 4
/dev/ttyS1, UART: 16550A, Port: 0x02f8, IRQ: 3
/dev/ttyS2, UART: 16650, Port: 0xdf30, IRQ: 30
/dev/ttyS3, UART: 16650, Port: 0xdf38, IRQ: 30
/dev/ttyS4, UART: 16650, Port: 0xdf00, IRQ: 30      <-- Why is this one not ttyS2?
/dev/ttyS5, UART: 16650, Port: 0xdf08, IRQ: 30
/dev/ttyS6, UART: 16650, Port: 0xdf10, IRQ: 30
/dev/ttyS7, UART: 16650, Port: 0xdf18, IRQ: 30
/dev/ttyS8, UART: 16650, Port: 0xdf20, IRQ: 30
/dev/ttyS9, UART: 16650, Port: 0xdf28, IRQ: 30

Looking in dmesg, it's finding them in the right order, but starting the lowest I/O port out at ttyS4:

# dmesg | grep ttyS
serial8250: ttyS0 at I/O 0x3f8 (irq = 4) is a 16550A
serial8250: ttyS1 at I/O 0x2f8 (irq = 3) is a 16550A
00:06: ttyS0 at I/O 0x3f8 (irq = 4) is a 16550A
00:07: ttyS1 at I/O 0x2f8 (irq = 3) is a 16550A
0000:05:00.0: ttyS4 at I/O 0xdf00 (irq = 30) is a ST16650
0000:05:00.0: ttyS5 at I/O 0xdf08 (irq = 30) is a ST16650
0000:05:00.0: ttyS6 at I/O 0xdf10 (irq = 30) is a ST16650
0000:05:00.0: ttyS7 at I/O 0xdf18 (irq = 30) is a ST16650
0000:05:00.0: ttyS8 at I/O 0xdf20 (irq = 30) is a ST16650
0000:05:00.0: ttyS9 at I/O 0xdf28 (irq = 30) is a ST16650
0000:05:00.0: ttyS2 at I/O 0xdf30 (irq = 30) is a ST16650
0000:05:00.0: ttyS3 at I/O 0xdf38 (irq = 30) is a ST16650

Trying to use setserial to change the port doesn't seem to work, it always reports that the device is busy (this is from a fresh reboot, nothing has accessed the device).

# setserial /dev/ttyS2 port 0xdf00
Cannot set serial info: Device or resource busy

Edit: Thanks to info provided by Gilles, I have it mostly working now using a udev that writes an ordered NAME by matching on the KERNEL name. The output from udevadm info shows that is the only parameter that can be used to uniquely identify the individual devices (ttyS[2-9] all report the same information, except for the KERNEL parameter).

# udevadm info -a -n /dev/ttyS2
  looking at device '/devices/pci0000:00/0000:00:07.0/0000:05:00.0/tty/ttyS2':
    KERNEL=="ttyS2"
    SUBSYSTEM=="tty"
    DRIVER==""

  looking at parent device '/devices/pci0000:00/0000:00:07.0/0000:05:00.0':
    KERNELS=="0000:05:00.0"
    SUBSYSTEMS=="pci"
    DRIVERS=="serial"
    ATTRS{vendor}=="0x494f"
    ATTRS{device}=="0x10a9"
    ATTRS{subsystem_vendor}=="0x0000"
    ATTRS{subsystem_device}=="0x0000"
    ATTRS{class}=="0x070002"
    ATTRS{irq}=="30"
    ATTRS{local_cpus}=="0000ff"
    ATTRS{local_cpulist}=="0-7"

My new udev rules:

SUBSYSTEM=="tty", DRIVERS=="serial", ATTRS{vendor}=="0x494f", KERNEL=="ttyS4", NAME="ttyS2"
# [snipped 7 more rules for each device]

Best Answer

You should be able to change the device names with a udev rule. Run udevadm info -a -n /dev/ttyS2 to obtain characteristics of your device. Find attributes that uniquely identify the multiport card, and one attribute that identifies the port. Then write udev rules for each port. The rules might look like this:

SUBSYSTEM=="tty", DRIVERS=="serial", ATTRS{vendor}=="Yoyodyne", ATTRS{port}=="0xdf00", NAME="ttyS2"
SUBSYSTEM=="tty", DRIVERS=="serial", ATTRS{vendor}=="Yoyodyne", ATTRS{port}=="0xdf80", NAME="ttyS3"
…

Run udevadm trigger (with the right --attr-match-… option) to reapply the rules to already-connected devices.

Related Question