Looking for a good 2xCOM ports PCIe adapter

Hi, FreeBSD Gurus!

Looking for a good 2 x COM ports PCIe adapter with better FreeBSD support on a driver level.

Any suggestions?

UPDATE:
I found old card near me in store, is it able to work in FreeBSD?

Looks like this is based on : WCH CH382L chip, and uart(4) support them natively.
 

Attachments

  • 0B38D0A3-0D7D-497E-B35B-80D20B52B3AA.jpeg
    0B38D0A3-0D7D-497E-B35B-80D20B52B3AA.jpeg
    128 KB · Views: 108
While not specific to serial adapters I made good experiences with stuff from Delock.
This would appear to be a viable option: https://www.delock.com/produkt/89555/merkmale.html
It's based on the AX99100 chipset which claims to be compatible with the good old 16950 chipset which in turn appears to be supported by uart(4).

I don't own this particular PCIe adapter so don't expect this to work out-of-the-box based on my information. However, in general serial interfaces are pretty much as simple as stuff gets so writing a driver for a non-supported chipset it is comparably easy. Especially if this chipset is compatible with the 16950 it might be as easy as adding the PCI identifier.

EDIT:
Seems like somebody already did that: https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=215837
Also this: https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=235016
 
UPDATE:
I found old card near me in store, is it able to work in FreeBSD?

Looks like this is based on : WCH CH382L chip, and uart(4) support them natively.

I took this as a statement that the chip would be supported. So when my dealer offered that one in a 2ser1par version, I ordered one. (The AX99100 version would cost twice as much.)

Now, it is not supported. Not rightaway, at least. But probably it can be made to work.

Somewhere on the web is the documentation paper for the chip. Some 15 pages, so that should be fine - if you can read it. It is all in chinese.

What I did:
  1. figure out the pci vendor and device id with pciconf. This is EEprogrammable, so it could be anything.
  2. add these values to the pucdata.c file. Copy the stance for the 'Feasso' device, this is somewhere said to be based on a similar WCH chip (the PCI version, without "e").
  3. There is a device driver CH38XDRV.ZIP online at WCH. This does not only contain M$ stuff, but also a Linux driver - and that is source code, and quite readable. Sadly it does cover only the serial devices, not the parallel.
  4. In that code is a table that describes the offsets of the actual devices within the ports array:
    Code:
            {                 // VenID                        DevID                                           SubVenID                                SubSysID 
                    VENDOR_ID_WCH_PCIE,     DEVICE_ID_WCH_CH382_2S1P,       SUB_VENDOR_ID_WCH_PCIE, SUB_DEVICE_ID_WCH_CH382_2S1P,  
                    // SerPort      IntrBar IntrOffset      IntrOffset1     IntrOffset2     IntrOffset3 Name                        BoardFlag 
                    2,                      0,              0xE9,           0x00,           0x00,           0x00,           "CH382_2S1P",   BOARDFLAG_NONE, 
                    { 
                            //      type    bar1    ofs1    len1            bar2    ofs2    len2    flags 
                            {       's',    0,              0xC0,   8,                      -1,             0,              0,              WCH_BOARD_CH382_2S1P  },                                                
                            {       's',    0,              0xC8,   8,                      -1,             0,              0,              WCH_BOARD_CH382_2S1P  }, 
                    }, 
            },
    (This should be 0xC0 and 0xC8 in the first resource.)
  5. Edit puc_cfg.c to provide the correct resource and offset as found.
  6. kldload puc does not work. It finds and attaches the card, but does not find any devices on it. This seems to be a problem on our side, with the device tree.
  7. With a GENERIC kernel (that has puc included) with the beforementioned changes, I was then able to get to this:
Code:
puc0: <DeLock 2ser1par CH 382L> port 0xd000-0xd0ff,0xd100-0xd103 mem 0xf0000000-0xf0007fff irq 18 at device 0.0 on pci6
uart2: <16x50 with 256 byte FIFO> at port 1 on puc0

It didn't find the second port, neither the parallel - but it did correctly detect the first as a 16750 (256 byte fifo). I then attached an old modem, and with cu I could type in "AT&V" and cleanly read the modem config.

So while there is some more work to do, this looks like a go. I am mainly interested in the parallel port: a serial port gives you only two programmable wires, and these work with ioctl() and could as well be obtained from an usb-to-serial adapter. A parallel port should give at least 8, maybe 12 wires, but these work via /dev/io and cannot normally obtained from an usb-to-parallel adapter. (I need these to switch my harddisk fans from smartctl -l scttempsts)
 
Be advised you may have to alter /boot/device.hints to enable some PCI/e serial cards.

I have commented out the uarts and had to add my own. Notice hints only entail uart0 and uart1.
You might have to manually add ports above those.

Some of this depends on legacy COM addresses or newer ones. How does the controller present the channels.
 
I went to check on if SIIG still made good cards and apparently they also use this chipset now
I use Siig 4 port cards but vid and pid must be added to pucdata.c
BUN1009X and BUN 1650X part numbers.


Here is one before patching on my RockPro64:
Code:
 pciconf -lv
pcib1@pci0:0:0:0:    class=0x060400 rev=0x00 hdr=0x01 vendor=0x1d87 device=0x0100 subvendor=0x0000 subdevice=0x0000
    vendor     = 'Rockchip Electronics Co., Ltd'
    device     = 'RK3399 PCI Express Root Port'
    class      = bridge
    subclass   = PCI-PCI
none0@pci0:1:0:0:    class=0x070002 rev=0x00 hdr=0x00 vendor=0x9710 device=0x9900 subvendor=0xa000 subdevice=0x1000
    vendor     = 'MosChip Semiconductor Technology Ltd.'
    device     = 'MCS9900 Multi-I/O Controller'
    class      = simple comms
    subclass   = UART
none1@pci0:1:0:1:    class=0x070002 rev=0x00 hdr=0x00 vendor=0x9710 device=0x9900 subvendor=0xa000 subdevice=0x1000
    vendor     = 'MosChip Semiconductor Technology Ltd.'
    device     = 'MCS9900 Multi-I/O Controller'
    class      = simple comms
    subclass   = UART
none2@pci0:1:0:2:    class=0x070002 rev=0x00 hdr=0x00 vendor=0x9710 device=0x9900 subvendor=0xa000 subdevice=0x1000
    vendor     = 'MosChip Semiconductor Technology Ltd.'
    device     = 'MCS9900 Multi-I/O Controller'
    class      = simple comms
    subclass   = UART
none3@pci0:1:0:3:    class=0x070002 rev=0x00 hdr=0x00 vendor=0x9710 device=0x9900 subvendor=0xa000 subdevice=0x1000
    vendor     = 'MosChip Semiconductor Technology Ltd.'
    device     = 'MCS9900 Multi-I/O Controller'
    class      = simple comms
    subclass   = UART
 
The devices that the uart(4) driver grabs directly are listed in sys/dev/uart/uart_bus_pci.c.

The devices that the puc(4) driver acts as a shim to connect PCI serial ports to the uart(4) driver are listed in sys/dev/puc/pucdata.c.

Some entries are for specific cards, and some are for chipsets. So some research in the tech specs for your chosen card may be required.

I'll go out on a limb and suggest that 16550 or a 16950 chipset from a quality vendor like Startech is a good bet.
 
The printer prints! My HP5p likes it.

This is again not a problem with the card, but something with our code: when trying to attach in dev/ppc/ppc.c, the callback into puc_bus_alloc_resource() gets a zero address.

This is the serial, that works:

Code:
enter puc_bus_alloc_resource
      rid=0 start=0 end=-1 count=8
      dev=-8796048163584 child=-8796048163840
      set puc_port to child->ivars (-8796051897216)
      puc_port p_nr=1, p_type=2, p_bar/rid=16 p_bar/type=4 rres=-8796048286592
      type is rres
      not yet assigned, setting to originator
      success!

And this is the parallel:

Code:
enter puc_bus_alloc_resource
      rid=0 start=0 end=-1 count=8
      dev=-8796048163584 child=-8796048164352
      set puc_port to child->ivars (-8796051897008)
      puc_port p_nr=3, p_type=2, p_bar/rid=24 p_bar/type=4 rres=0
      type is rres
      res is NULL *FAIL*

So, instead of failing after setting res = port->p_rres, I thought, why not just copy the similar looking res = port->p_bar->b_res. Then it complained about a wrong ownership, and I did override that also. And I very much expected the kernel crash, because I have no idea what I'm doing - not of the PCI logic, not much of the devicetree, and absolutely not what these apparently 43-bit address pointers might mean.
But then instead:
Code:
ppc1: using extended I/O port range
ppc1: <Parallel port> at port 3 on puc0
ppc1: Generic chipset (NIBBLE-only) in COMPATIBLE mode
ppbus0: <Parallel port bus> on ppc1
lpt0: <Printer> on ppbus0
lpt0: Polled port

So, bar/rid = 24, offset = 0 is indeed the parallel port location of the CH382L.

I suppose the kernel crash will come when I shutdown now, as there should be one object missing in the device tree. But the printer prints.

Be advised you may have to alter /boot/device.hints to enable some PCI/e serial cards.

Yes, I did set these to the lowest possible standard, all features off:
Code:
hint.ppc.0.flags="0xe0"
hint.ppc.1.flags="0xe0"
hint.ppc.2.flags="0xe0"
 
I'll go out on a limb and suggest that 16550 or a 16950 chipset from a quality vendor like Startech is a good bet.
Ah. Yeah, one can do that. But for just using the wires to drive a transistor (i.e. soldering on it anyway, because the outlet is on the backpanel, and I need it inside) the chinese stuff should do.
 
Be advised you may have to alter /boot/device.hints to enable some PCI/e serial cards.

I have commented out the uarts and had to add my own. Notice hints only entail uart0 and uart1.
You might have to manually add ports above those.

Some of this depends on legacy COM addresses or newer ones. How does the controller present the channels.
I think that UART hints are actually needed only for ISA-style UARTs and even then the on-board UARTs are typically described in ACPI tables anyway.
For PCI(e) add-on cards no hints are needed, such UARTs are enumerated automatically.
The only reason to have a UART hint is to set any special flags.
 
Okay, here it is. WCH CH382L now working. Not really checked if the rclk is correct, or the interrupts working well - I just need the wires, anyway.

Code:
--- a/sys/dev/puc/pucdata.c
+++ b/sys/dev/puc/pucdata.c
@@ -68,6 +68,7 @@ static puc_config_f puc_config_siig;
 static puc_config_f puc_config_sunix;
 static puc_config_f puc_config_timedia;
 static puc_config_f puc_config_titan;
+static puc_config_f puc_config_wch;
 
 const struct puc_cfg puc_pci_devices[] = {
        {   0x0009, 0x7168, 0xffff, 0,
@@ -1278,6 +1279,13 @@ const struct puc_cfg puc_pci_devices[] = {
            PUC_PORT_4S, 0x10, 0, 8,
            .config_function = puc_config_icbook
        },
+
+       {   0x1c00, 0x3250, 0xffff, 0,
+           "WCH CH 382 Multi-I/O",
+           DEFAULT_RCLK,
+           PUC_PORT_2S1P, 0x10, 4, 8,
+           .config_function = puc_config_wch
+       },
        { 0xffff, 0, 0xffff, 0, NULL, 0 }
 };
 
@@ -1867,3 +1875,35 @@ puc_config_titan(struct puc_softc *sc __unused, enum puc_cfg_cmd cmd,
        }
        return (ENXIO);
 }
+
+static int
+puc_config_wch(struct puc_softc *sc, enum puc_cfg_cmd cmd, int port,
+    intptr_t *res)
+{
+       const struct puc_cfg *cfg = sc->sc_cfg;
+
+       /* these are currently specific to the CH 382L */
+       switch (cmd) {
+       case PUC_CFG_GET_OFS:
+               if (port == 2)
+                       *res = 0;
+               else
+                       *res = 0xc0 + port * cfg->d_ofs;
+               return(0);
+       case PUC_CFG_GET_RID:
+               if (port == 1) {
+                       *res = cfg->rid;
+                       return (0);
+               }
+               break;
+       case PUC_CFG_GET_LEN:
+               if (port == 2) {
+                       *res = 4;
+                       return (0);
+               }
+               break;
+       default:
+               break;
+       }
+       return (ENXIO);
+}

The difficulty was that puc code expects PCI portranges be 8 byte wide by default (unless configured otherwise) and then let the ppc driver find out if the lpt is 4 byte or 8 byte wide. But this card provides only 4 byte for LPT on the pci:
Code:
# pciconf -cBbl
puc0@pci0:6:0:0:        class=0x070005 rev=0x10 hdr=0x00 vendor=0x1c00 device=0x3250 subvendor=0x1c00 subdevice=0x3250
    bar   [10] = type I/O Port, range 32, base 0xd000, size 256, enabled
    bar   [14] = type Prefetchable Memory, range 32, base 0xf0000000, size 32768, enabled
    bar   [18] = type I/O Port, range 32, base 0xd100, size 4, enabled
 
I've used PMc's patches for a WCH CH382L based 2 port serial (no parallel port) on a Dell r210 II with no luck:
Code:
puc0@pci0:1:0:0:    class=0x070005 rev=0x10 hdr=0x00 vendor=0x1c00 device=0x3253 subvendor=0x1c00 subdevice=0x3253
    bar   [10] = type I/O Port, range 32, base 0x2000, size 256, enabled
    bar   [14] = type Prefetchable Memory, range 32, base 0xc5100000, size 32768, enabled
    bar   [18] = type I/O Port, range 32, base 0x2100, size 4, enabled

uart0: <16550 or compatible> port 0x3f8-0x3ff irq 4 flags 0x10 on acpi0
uart1: <16550 or compatible> port 0x2f8-0x2ff irq 3 on acpi0
uart2: <16x50 with 256 byte FIFO> at port 1 on puc0
uart3: <16x50 with 256 byte FIFO> at port 2 on puc0
I'm using a different device ID as well as using sub vendor/sub device ID
Code:
        {   0x1c00, 0x3253, 0x1c00, 0x3253, 
            "WCH CH 382 Multi-I/O",
            DEFAULT_RCLK,
            PUC_PORT_2S, 0x10, 4, 8,
        },
It only seems to work once as in one byte per port and then it also seems to break processing on the built in serial port. It requires rebooting the system to get the more bytes.

Getting this driver properly fixed and supported is important as it seems to be the the most common low cost serial/parallel PCI-e chipset in current products. I spent some time hunting for supported devices and a vast majority of the ones listed in
sys/dev/puc/pucdata.c are older PCI bus cards. The CH382L chip has a pins on it to change how many serial and parallel ports it has active. The 0x3250 appears to be the 2S1P and the 0x3253 is the 2S. There may be other combos.
 
Hm, strange. I did try to read data from a modem, and I could enter the at&v, and read the correctly formatted modem config (some 500 byte). I didn't test further, because I am mainly interested in using the parallel port pins for switching things. (That is put on hold now due to other priorities.)
This is now something I had in the back of my mind: after the devices get detected, will then the driver get properly invoked and the interrupts dispatched and whatever else is needed?
 
Hm, strange. I did try to read data from a modem, and I could enter the at&v, and read the correctly formatted modem config (some 500 byte). I didn't test further, because I am mainly interested in using the parallel port pins for switching things. (That is put on hold now due to other priorities.)
This is now something I had in the back of my mind: after the devices get detected, will then the driver get properly invoked and the interrupts dispatched and whatever else is needed?
Modems will usually autobaud so if the clocks are off by a factor of 4x, it may not matter and might still work. If you connect at 57600, the echo should be instant, at 300 baud the echo will be about at a slow typing speed. I was using /dev/cuau2 and cuau3 which are also /dev/ttyu2 and ttyu3. I used cu -s 115200 -l /dev/cuau2. The built in cuau0 and usb port cuau0 worked fine. To test a loop back on a 9 pin connection, I use a tiny phillps screw driver to short pin 2&3 which should cause anything typed in cu to echo back. Looking at the connector with 5 on the top, 3 is in the middle and two is just to its left.

I also tried removing the lines to pucdata.c and adding an entry to uart_bus_pci.c attempting to get one serial port working but that has resulted in a lockup at boot:
Code:
static const struct pci_id pci_ns8250_ids[] = {
{ 0x1c00, 0x3253, 0x1c00, 0x3253, "WCH maybe", 0x10, 0x1 * DEFAULT_RCLK },
 
I would be willing to send someone one of these serial adapters if the shipping isn't too bad. They can be had for $15 so they are one of the cheapest serial boards out there.
 
Back
Top