Re: [PATCH v4] usbserial: cp210x - icount support for parity error checking

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Wed, Jul 01, 2020 at 09:28:25PM +0200, Jerry wrote:
> Johan Hovold wrote on 7/1/20 5:42 PM:
> > It would be better if could detect both types of overrun.
> >
> > Did you try moving the purge command at close to after disabling the
> > uart?
> >
> > Or perhaps we can add a "dummy" comm-status command after disabling the
> > uart?

> I try to describe more details about this overrun problem:
> 1) I tried only CP2102 because our company uses it, I have no idea whether 
> the same apply for CP2104,2105... or not, I don't have another chip.
> 2) Maybe I should note I'm always using even parity (because of STM32 
> bootloader protocol).
> 3) I thought the problem is created by unreaded data when closing because 
> overrun was reported after closing if GET_COMM_STATUS shown positive 
> ulAmountInInQueue before closing. Later I discovered that if I close the 
> port, wait, send some data from outside, then open it, I will also get 
> HW_OVERRUN flag.
> 4) So currently I suppose that problem is usually created by any incoming 
> data after disabling interface. If I remove UART_DISABLE from close method, 
> no overrun ever reported.
> 5) Unfortunately this overrun is not reported immediately after port 
> opening but later after receiving first byte. I didn't find any way how to 
> purge nor dummy read this flag before transmitting data.
> 6) I didn't find this problem in a chip errata and nobody complaining about it.
> 7) I successfully reproduced the same problem in MS Windows 10. If some 
> data arrived to closed port, then I open it, everything is OK but after 
> sending request and receiving reply I often get overrun indication from 
> Win32 API ClearCommError()
> 8) I removed HW_OVERRUN checking because I don't want to break anything but 
> maybe Linux driver should work the same way as Windows and don't hide this 
> problem?
> 9) I needed just to detect parity error from user space and things 
> complicate.  :-)

Heh, yeah, it tends to be that way. :) But thanks for the great summary
of your findings!

I think it probably makes most sense to keep the error in as it's a
quirk of the device rather than risk missing an actual overrun.

The problem here is that we're sort of violating the API and turning
TIOCGICOUNT into a polling interface instead of just returning our error
and interrupt counters. This means will always undercount any errors for
a start.

The chip appears to have a mechanism for reporting errors in-band, but
that would amount to processing every character received to look for the
escape char, which adds overhead. I'm guessing that interface would also
insert an overrun error when returning the first character.

But perhaps that it was we should be using as it allows parity the
errors to be reported using the normal in-band methods. If we only
enable it when parity checking is enabled then the overhead seems
justified.

I did a quick test here and the event insertion appears to work well for
parity errors. Didn't manage to get it to report break events yet, and
modem-status changes are being buffered and not reported until the next
character. But in theory we could expand the implementation to provide
more features later.

I'll see if I can cook something up quickly.

> >>    /*
> >> - * Read how many bytes are waiting in the TX queue.
> >> + * Read how many bytes are waiting in the TX queue and update error counters.
> >>     */
> >> -static int cp210x_get_tx_queue_byte_count(struct usb_serial_port *port,
> >> -		u32 *count)
> >> +static int cp210x_get_comm_status(struct usb_serial_port *port,
> >> +		u32 *tx_count)
> >>    {
> >>    	struct usb_serial *serial = port->serial;
> >>    	struct cp210x_port_private *port_priv = usb_get_serial_port_data(port);
> >> @@ -855,7 +865,17 @@ static int cp210x_get_tx_queue_byte_coun
> >>    			0, port_priv->bInterfaceNumber, sts, sizeof(*sts),
> >>    			USB_CTRL_GET_TIMEOUT);
> >>    	if (result == sizeof(*sts)) {
> >> -		*count = le32_to_cpu(sts->ulAmountInOutQueue);
> >> +		u32 flags = le32_to_cpu(sts->ulErrors);
> >> +		if (flags & CP210X_SERIAL_ERR_BREAK)
> >> +			port->icount.brk++;
> >> +		if (flags & CP210X_SERIAL_ERR_FRAME)
> >> +			port->icount.frame++;
> >> +		if (flags & CP210X_SERIAL_ERR_QUEUE_OVERRUN)
> >> +			port->icount.overrun++;
> >> +		if (flags & CP210X_SERIAL_ERR_PARITY)
> >> +			port->icount.parity++;
> >> +		if (tx_count)
> >> +			*tx_count = le32_to_cpu(sts->ulAmountInOutQueue);
> >>    		result = 0;
> >>    	} else {
> >>    		dev_err(&port->dev, "failed to get comm status: %d\n", result);
> >> @@ -873,13 +893,26 @@ static bool cp210x_tx_empty(struct usb_s
> >>    	int err;
> >>    	u32 count;
> >>    
> >> -	err = cp210x_get_tx_queue_byte_count(port, &count);
> >> +	err = cp210x_get_comm_status(port, &count);
> >>    	if (err)
> >>    		return true;
> >>    
> >>    	return !count;
> >>    }
> > It seems a bit weird to be updating the error counters while polling
> > for tx-empty during close. How about having cp210x_get_comm_status()
> > take two u32 pointers for tx_count and errors and only pass in one or
> > the other from cp210x_tx_empty() and cp210x_get_icount() respectively?

> I suppose that GET_COMM_STATUS reads and CLEAR pending error flags (not 
> explained in datasheet but behaves that way). So if some errors are 
> reported during cp210x_tx_empty() it can be either counted immediately or 
> lost. I'm not sure if cp210x_tx_empty() is called ONLY during close but 
> seems be more consistent to count every detected error regardless who calls 
> GET_COMM_STATUS.

tx_empty() is called when waiting for data to be drained for example
during close but also on when changing terminal settings with TCSADRAIN
or on tcdrain().

Since we wouldn't be counting errors during normal operation it seems
arbitrary to be counting during these seemingly unrelated calls. Better
to only poll from TIOCGICOUNT, if we decide to go with that approach at
all.

> >> +static int cp210x_get_icount(struct tty_struct *tty,
> >> +		struct serial_icounter_struct *icount)
> >> +{
> >> +	struct usb_serial_port *port = tty->driver_data;
> >> +	int result;
> >> +
> >> +	result = cp210x_get_comm_status(port, NULL);
> >> +	if (result)
> >> +		return result;
> > And then you update the error counters here, preferably under the port
> > lock.

> I wasn't sure about port lock. I looked in several usb drivers (ftdi, 
> pl2303) and it seems that port->icount.xxx increments are usually done out 
> of lock. But if you wish I put increments into 
> spin_lock_irqsave(&port->lock, flags), correct?

Correct.

It's quite possible that that's missing elsewhere, but at least those
counters are updated in completion callbacks which do not run in
parallel unlike ioctls(), which are not serialised.

Johan



[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux