# PCIe 6 port serial card



## bmth (Nov 30, 2017)

Hello, everyone!

I have STLab I-472 PCIe 6 port serial card with Moschip MCS9900 and Moschip MCS9922 chips on it, and I'm trying to get it working with FreeBSD 10.3 (amd64, generic kernel).

According to what I see in the sources of uart driver both chips are supported by the driver:

```
static const struct pci_id pci_ns8250_ids[] = {
...
{ 0x9710, 0x9900, 0xa000, 0x1000,
        "MosChip MCS9900 PCIe to Peripheral Controller", 0x10 },
...
{ 0x9710, 0x9922, 0xa000, 0x1000,
        "MosChip MCS9922 PCIe to Peripheral Controller", 0x10 },
...
};
```

But after the system boots, I can only see two of six serial ports:

```
#ls {/dev/ttyu*,/dev/cuau*}
/dev/cuau1
/dev/cuau1.init
/dev/cuau1.lock
/dev/cuau2
/dev/cuau2.init
/dev/cuau2.lock
/dev/ttyu1
/dev/ttyu1.init
/dev/ttyu1.lock
/dev/ttyu2
/dev/ttyu2.init
/dev/ttyu2.lock
```
(I excluded cuau0 and ttyu0 devices from the command output because they are unrelated to the card).

Apparently, the uart driver is not installed for the first two ports (and last two ports, too - only ports number 2 and 3 are recognized):

```
#grep 'pci3\|uart' /var/run/dmesg.boot
pci3: <ACPI PCI bus> on pcib3
pcib3: allocated bus range (3-3) for rid 0 of pci3
pci3: domain=0, physical bus=3
pci3: <simple comms> at device 0.0 (no driver attached)
pci3: <simple comms> at device 0.1 (no driver attached)
uart1: <MosChip MCS9900 PCIe to Peripheral Controller> port 0x9000-0x9007 mem 0xfa006000-0xfa006fff,0xfa007000-0xfa007fff irq 19 at device 0.2 on pci3
uart1: fast interrupt
uart1: PPS capture mode: DCDinvalid
uart2: <MosChip MCS9900 PCIe to Peripheral Controller> port 0x9400-0x9407 mem 0xfa008000-0xfa008fff,0xfa009000-0xfa009fff irq 16 at device 0.3 on pci3
uart2: fast interrupt
uart2: PPS capture mode: DCDinvalid
uart0: <16550 or compatible> port 0x3f8-0x3ff irq 4 flags 0x10 on acpi0
uart0: console (19200,n,8,1)
uart0: fast interrupt
uart0: PPS capture mode: DCDinvalid
uart: uart0 already exists; skipping it
```

My PCI bus configuration:

```
#pciconf -lv
none1@pci0:3:0:0:       class=0x078000 card=0x3002a000 chip=0x99009710 rev=0x00 hdr=0x00
    vendor     = 'MosChip Semiconductor Technology Ltd.'
    class      = simple comms
none2@pci0:3:0:1:       class=0x078000 card=0x3002a000 chip=0x99009710 rev=0x00 hdr=0x00
    vendor     = 'MosChip Semiconductor Technology Ltd.'
    class      = simple comms
uart1@pci0:3:0:2:       class=0x078000 card=0x1000a000 chip=0x99009710 rev=0x00 hdr=0x00
    vendor     = 'MosChip Semiconductor Technology Ltd.'
    class      = simple comms
uart2@pci0:3:0:3:       class=0x078000 card=0x1000a000 chip=0x99009710 rev=0x00 hdr=0x00
    vendor     = 'MosChip Semiconductor Technology Ltd.'
    class      = simple comms
```

The puc driver is loaded.

Why does the uart driver not recognize the rest of the card ports? Maybe it requires some configuration? The Handbook recommends to turn on COM_MULTIPORT, but as far as I understand, this option is for sio driver.


----------



## bmth (Nov 30, 2017)

After some reading of MCS9900 and MCS9922 datasheets I've discovered the following:


MCS9900 and MCS9922 are essentially the same chip, the only difference is that MCS9900 supports either 4 serial, or 2 serial + 1 parallel ports configuration, and MCS9922 is hardlocked to 2 serial ports configuration.
Two MCS9900/MCS9922 can be combined using cascade interface into master+slave configuration, thus providing the end user with a variety of different options: 8 serial / 6 serial + 1 parallel / 4 serial + 2 parallel. In my particular case it's 6 serial ports configuration: the first four ports are served by MCS9900 chip, the second two ports are provided by MCS9922 chip.
Only the master chip MCS9900 interacts with PCI bus, giving 4 pci device functions. The slave chip interacts with the master chip, and it's functions are mapped to the functions of the master. In my 6 serial ports configurations functions are distrubited in the following way:
Function 0: serial port 0, serial port 5
Function 1: serial port 1, serial port 6
Function 2: serial port 2
Function 3: serial port 3

So this is why only ports 2 and 3 are recognized by the uart driver.
Meanwhile port mapping is somewhat trivial for such cascaded combination:


```
Function 0   Function 1   Function 2   Function 3
BAR0  SP1 I/O      SP2 I/O      SP3 I/O      SP4 I/O
BAR1  SP1 MEM      SP2 MEM      SP3 MEM      SP4 MEM
BAR2  --           --           --           --
BAR3  SP5 I/O      SP6 I/O      --           --
BAR4  SP5 MEM      SP6 MEM      --           --
BAR5  GPIO         GPIO         --           GPIO
```
/sorry, have no idea how to post tables here =(/
So, it looks like that the driver should be told someway, that first two functions of the device have two serial ports inside, and told base I/O ports for the serial ports. The rest is all the same. How can that be done?​Here's the output of pciconf with bars:

```
none1@pci0:3:0:0:       class=0x078000 card=0x3002a000 chip=0x99009710 rev=0x00 hdr=0x00
    vendor     = 'MosChip Semiconductor Technology Ltd.'
    class      = simple comms
    bar   [10] = type I/O Port, range 32, base 0x5000, size 8, enabled
    bar   [14] = type Memory, range 32, base 0xfc000000, size 4096, enabled
    bar   [1c] = type I/O Port, range 32, base 0x5400, size 8, enabled
    bar   [20] = type Memory, range 32, base 0xfc001000, size 4096, enabled
    bar   [24] = type Memory, range 32, base 0xfc002000, size 4096, enabled
none2@pci0:3:0:1:       class=0x078000 card=0x3002a000 chip=0x99009710 rev=0x00 hdr=0x00
    vendor     = 'MosChip Semiconductor Technology Ltd.'
    class      = simple comms
    bar   [10] = type I/O Port, range 32, base 0x5800, size 8, enabled
    bar   [14] = type Memory, range 32, base 0xfc003000, size 4096, enabled
    bar   [1c] = type I/O Port, range 32, base 0x5c00, size 8, enabled
    bar   [20] = type Memory, range 32, base 0xfc004000, size 4096, enabled
    bar   [24] = type Memory, range 32, base 0xfc005000, size 4096, enabled
uart1@pci0:3:0:2:       class=0x078000 card=0x1000a000 chip=0x99009710 rev=0x00 hdr=0x00
    vendor     = 'MosChip Semiconductor Technology Ltd.'
    class      = simple comms
    bar   [10] = type I/O Port, range 32, base 0x6000, size 8, enabled
    bar   [14] = type Memory, range 32, base 0xfc006000, size 4096, enabled
    bar   [24] = type Memory, range 32, base 0xfc007000, size 4096, enabled
uart2@pci0:3:0:3:       class=0x078000 card=0x1000a000 chip=0x99009710 rev=0x00 hdr=0x00
    vendor     = 'MosChip Semiconductor Technology Ltd.'
    class      = simple comms
    bar   [10] = type I/O Port, range 32, base 0x6400, size 8, enabled
    bar   [14] = type Memory, range 32, base 0xfc008000, size 4096, enabled
    bar   [24] = type Memory, range 32, base 0xfc009000, size 4096, enabled
```


----------



## Snurg (Nov 30, 2017)

It's a few years ago I had to deal with Moschip-equipped multi-I/O cards, so I only remember roughly.
You know that these chips need to be initialized before their ports become [fully] active.
You can verify this in a simple way: first boot DOS/windows with the boards' drivers. Then boot from a FreeBSD dvd, usb stic or the like. You will see that the 16550-compatible ports are then present and usable without any drivers.

In my impression the difficult thing with Moschip interfaces is just to get them initialized correctly. Then the OS can take over using its standard drivers.
And there are quite a lot of Moschip variants, differing only in the number and type of ports. 
The driver problem now is, it has to find out which chip is installed and initialize the registers of that particular chip that control the individual ports (i.e. I/O address, IRQ and maybe even more) to make the embedded 16550s etc "appear".
Your challenge is to find out what registers your actual chip has available and set them all the way you like.

And then your findings can help the community, too, by adding support for that particular Moschip variant.


----------



## bmth (Dec 1, 2017)

Snurg said:


> The driver problem now is, it has to find out which chip is installed and initialize the registers of that particular chip that control the individual ports (i.e. I/O address, IRQ and maybe even more) to make the embedded 16550s etc "appear".


Looks like it does explain why device probe failed for the first two ports.

UPDATE: That idea appeared to be wrong.

When I analyzed sources of uart driver, I did not take notice that the driver's probe method not only checks for vendor id and device id, but it also checks for subvendor id and subdevice id.

According to datasheets of MCS9900 and MCS9922, subvendor id for those chips is always A000, but subdevice id depends on configuration:

0000 - no ports at given pci device function
1000 - single serial port
2000 - single parallel port
3002 - serial + serial
3011 - serial (master) + parallel (slave)
3012 - parallel (master) + serial (slave)
3020 - parallel + parallel
Since I have serial + serial configuration, I've supplemented pci_ns8250_id table with data for my device:

```
// this was in the table before
{ 0x9710, 0x9900, 0xa000, 0x1000,
        "MosChip MCS9900 PCIe to Peripheral Controller", 0x10 },
// this was added by me
{ 0x9710, 0x9900, 0xa000, 0x3002,
        "MosChip MCS9900 PCIe to Peripheral Controller", 0x10 },
```
Voila, now I have four serial ports, which is not six yet, but much better than only two (two times better, I think).

```
uart1@pci0:3:0:0:       class=0x078000 card=0x3002a000 chip=0x99009710 rev=0x00 hdr=0x00
    vendor     = 'MosChip Semiconductor Technology Ltd.'
    class      = simple comms
uart2@pci0:3:0:1:       class=0x078000 card=0x3002a000 chip=0x99009710 rev=0x00 hdr=0x00
    vendor     = 'MosChip Semiconductor Technology Ltd.'
    class      = simple comms
uart3@pci0:3:0:2:       class=0x078000 card=0x1000a000 chip=0x99009710 rev=0x00 hdr=0x00
    vendor     = 'MosChip Semiconductor Technology Ltd.'
    class      = simple comms
uart4@pci0:3:0:3:       class=0x078000 card=0x1000a000 chip=0x99009710 rev=0x00 hdr=0x00
    vendor     = 'MosChip Semiconductor Technology Ltd.'
    class      = simple comms
```
Is there any way to build uart driver as a separate module? And thereafter rebuild only that module, but not the whole kernel? Rebuilding the whole kernel and then rebooting to check if my assumptions are right makes the progress very slow (if possible at all) =(


----------



## bmth (Dec 2, 2017)

Well, the issue is solved thanks to Marcel Moolenaar advise to take a look at puc driver, not uart driver.

The solution:

uart driver is left as is, that dirty hack of pci_ns8250_id table is not needed
puc driver is modified: description for mcs9900 + mcs9922 chip combination must be added to puc_pci_devices table contained in file pucdata.c:


```
const struct puc_cfg puc_pci_devices[] = {
  ...
    {   0x9710, 0x9865, 0xa000, 0x3020,
       "NetMos NM9865 Dual 1284 Printer port",
       DEFAULT_RCLK,
       PUC_PORT_2P, 0x10, 4, 0,
    },

// i've added this :

    {   0x9710, 0x9900, 0xa000, 0x3002,
       "MosChip MCS9900 Dual UART port",
       DEFAULT_RCLK,
       PUC_PORT_2S, 0x10, 12, 0,
    },

// ^^ i've added this ^^

    {   0xb00c, 0x021c, 0xffff, 0,
       "IC Book Labs Gunboat x4 Lite",
       DEFAULT_RCLK,
       PUC_PORT_4S, 0x10, 0, 8,
       .config_function = puc_config_icbook
    },
  ...
};
```

Now all six ports are working, non-combined ports are driven by uart driver, and combined ports are driven by puc, then by uart driver:

```
#pciconf -lv
puc0@pci0:3:0:0:        class=0x078000 card=0x3002a000 chip=0x99009710 rev=0x00 hdr=0x00
    vendor     = 'MosChip Semiconductor Technology Ltd.'
    class      = simple comms
puc1@pci0:3:0:1:        class=0x078000 card=0x3002a000 chip=0x99009710 rev=0x00 hdr=0x00
    vendor     = 'MosChip Semiconductor Technology Ltd.'
    class      = simple comms
uart5@pci0:3:0:2:       class=0x078000 card=0x1000a000 chip=0x99009710 rev=0x00 hdr=0x00
    vendor     = 'MosChip Semiconductor Technology Ltd.'
    class      = simple comms
uart6@pci0:3:0:3:       class=0x078000 card=0x1000a000 chip=0x99009710 rev=0x00 hdr=0x00
    vendor     = 'MosChip Semiconductor Technology Ltd.'
    class      = simple comms
#ls /dev/{cuau*,ttyu*}
/dev/cuau0      /dev/cuau1.lock /dev/cuau3.init /dev/cuau5      /dev/cuau6.lock /dev/ttyu1.init /dev/ttyu3      /dev/ttyu4.lock /dev/ttyu6.init
/dev/cuau0.init /dev/cuau2      /dev/cuau3.lock /dev/cuau5.init /dev/ttyu0      /dev/ttyu1.lock /dev/ttyu3.init /dev/ttyu5      /dev/ttyu6.lock
/dev/cuau0.lock /dev/cuau2.init /dev/cuau4      /dev/cuau5.lock /dev/ttyu0.init /dev/ttyu2      /dev/ttyu3.lock /dev/ttyu5.init
/dev/cuau1      /dev/cuau2.lock /dev/cuau4.init /dev/cuau6      /dev/ttyu0.lock /dev/ttyu2.init /dev/ttyu4      /dev/ttyu5.lock
/dev/cuau1.init /dev/cuau3      /dev/cuau4.lock /dev/cuau6.init /dev/ttyu1      /dev/ttyu2.lock /dev/ttyu4.init /dev/ttyu6
```
But I still have an issue with one port (device cuau2/ttyu2, port number 5 on card, if numeration starts with 1, the first of two ports of slave 9922 chip) - it works __very__ slow: after sending few bytes I need to wait about a minute to be able to send next few bytes. What can be a cause for the issue? All other ports work just fine.


----------



## Snurg (Dec 2, 2017)

Wow !
You did great research and I am sure your work will become part of the FreeBSD kernel soon!



> Is there any way to build uart driver as a separate module? And thereafter rebuild only that module, but not the whole kernel? Rebuilding the whole kernel and then rebooting to check if my assumptions are right makes the progress very slow (if possible at all) =(


I know no other way than to remove the module (and all others that pull it in as a loadable module at system start) from the kernel configuration file (more info in handbook, chapter is named "building a custom kernel" or the like).



> What can be a cause for the issue?


Regarding the delay the slave chip port #1 experiences, what about posting links to the datasheets you found, so that interested people can take a look themselves, too?


----------



## bmth (Dec 2, 2017)

Snurg said:


> Regarding the delay the slave chip port #1 experiences, what about posting links to the datasheets you found, so that interested people can take a look themselves, too?


It's one of the first links that google gives: MCS9900, MCS9922 (both at chips manufacturer site http://www.asix.com.tw/). Yeh, it says that you need to be an authorized customer or distributor, and that scares a lot, but it really just needs registration to download datasheets.

Regarding the delay itself - one of the slave chip ports works normally, it's the second port of the slave chip, and the first port produces those huge delays. Looks like this port is busy with something else ))


----------

