WICG / serial

Serial ports API for the platform.
https://wicg.github.io/serial/
Other
255 stars 46 forks source link

Document unexpected setSignals behaviour due to RTS bug in usbser.sys #173

Open codembed opened 1 year ago

codembed commented 1 year ago

The generic CDC-ACM driver usbser.sys in Windows 8.1, 10 and 11 has a bug when updating the RTS line (this seems to be a known bug that has been unresolved for many years).

For this specific driver the following SerialPort methods do not modify RTS:

.setSignals({requestToSend: false});  // RTS unchanged (default output - asserted?)
.setSignals({requestToSend: true});   // RTS unchanged

The following changes RTS, however RTS is set to the previous value:

.setSignals({requestToSend: true, dataTerminalReady: true});   // RTS unchanged
.setSignals({requestToSend: false, dataTerminalReady: true});  // RTS asserted (previous value)
.setSignals({requestToSend: true, dataTerminalReady: true});   // RTS negated (previous value)
.setSignals({requestToSend: true, dataTerminalReady: true});   // RTS asserted (previous value)

In USB traffic, I have noticed other windows serial terminals tend to issue the CDC SetControlLineState request multiple times in an attempt to circumvent the (undesirable) behaviour of usbser.sys

For web serial, an inelegant workaround is to issue the request twice in succession, while including a DTR value (either true or false) in the update 'mask'. For example:

// Assert RTS
.setSignals({requestToSend: true, dataTerminalReady: true});
.setSignals({requestToSend: true, dataTerminalReady: true});   // output asserted here (previous value)

// Negate RTS
.setSignals({requestToSend: false, dataTerminalReady: true});
.setSignals({requestToSend: false, dataTerminalReady: true});   // output negated here (previous value)

I suppose it might be possible to write a specific setSignals handler when usbser.sys is detected as the device driver?

reillyeon commented 1 year ago

Can you provide a reference to previous reports of this Windows bug?

If you observe that multiple CDC-ACM SetControlLineState requests are sent then is it possible that the Windows driver is behaving correctly when EscapeCommFunction() is called but some devices are ignoring the request when it is sent the first time? If this were a driver bug then I would expect that the first SetControlLineState request didn't include the correct RTS flag state requested by the application but later ones would. Is that the observed behavior?

Examples of hardware which exhibits this behavior would also be helpful. All the dedicated USB-to-serial converter chips I have are examples of the proprietary designs like FTDI, PL2303 and CP2103.

codembed commented 1 year ago

Your second conjecture is correct.

Consider the following code sequence of 8 setSignals calls with 100ms delays between.

await port.setSignals({requestToSend: true, dataTerminalReady: true});   // RTS unchanged (default output - asserted)
await sleep(100);
await port.setSignals({requestToSend: false, dataTerminalReady: true});  // RTS asserted (previous value)
await sleep(100);
await port.setSignals({requestToSend: true, dataTerminalReady: true});   // RTS negated (previous value)
await sleep(100);
await port.setSignals({requestToSend: true, dataTerminalReady: true});   // RTS asserted (previous value)
await sleep(100);
// Negate RTS
await port.setSignals({requestToSend: false, dataTerminalReady: true});
await sleep(100);
await port.setSignals({requestToSend: false, dataTerminalReady: true});  // output negated here (previous value)
await sleep(100);
// Assert RTS
await port.setSignals({requestToSend: true, dataTerminalReady: true});
await sleep(100);
await port.setSignals({requestToSend: true, dataTerminalReady: true});   // output asserted here (previous value)

The correct output should be: DTR RTS DTR DTR RTS DTR RTS DTR DTR DTR RTS DTR RTS

Here is the captured USB traffic for Windows 10 with usbser.sys, with its erroneous RTS values.

Windows 10

And equivalent traffic for Chrome OS Flex with its native CDC-ACM driver (with correct/acceptable output)

ChromeOS Flex

Notice, also, that the Chrome OS driver keeps a shadow copy of the DTR and RTS state, and only sends this to the device when a value actually changes. Hence, there are some idle periods with 200ms intervals between SetControlLineState transactions. But this is sensible since these signals are level (not edge) sensitive. In any case, with Chrome OS the RTS state is correctly updated at the relevant intervals.

On the other hand, you can see all 8 calls that usbser.sys sends to the CDC interface, and that the transmitted RTS state does not match the expected output. I also confirmed that with dataTerminalReady omitted from setSignals(), there is no CDC traffic at all.

I'm not aware of a readily available USB-Serial adapter that uses usbser.sys. However, I was able to configure Windows to use usbser.sys with some Samsung Android phones. I tested this on a Galaxy S5 and Galaxy S7. These phones present a "SAMSUNG Mobile USB Modem" or similar windows device, but this attaches to a standard CDC-ACM USB descriptor within the configuration collection, so you can run the 'update driver' wizard to manually pick "USB Serial Device" instead of "SAMSUNG Mobile USB Modem". The CDC-ACM interface should then show in device manager as a COM port rather than a modem. Hopefully this may be sufficient for you to verify the bug.

It seems this bug has been resident for even longer than I first thought - presumably since inception...Windows XP SP2 ?

https://answers.microsoft.com/en-us/windows/forum/all/issue-with-rts-control-via-usbsersys/4d3a43a0-cccf-49d1-9b22-b7123a512724

https://answers.microsoft.com/en-us/windows/forum/all/virtual-serial-port-usbsersys-is-not-sending/a086cbf6-2d0a-4c46-88fd-73865bc185f5

https://answers.microsoft.com/en-us/windows/forum/all/usbsersys-does-not-handle-rts-signal-correctly/e348047e-dacd-47d5-8e74-1fd2f275bebb

https://www.techtalkz.com/threads/usbser-sys-rts-problem.281170/