raspberrypi / linux

Kernel source tree for Raspberry Pi-provided kernel builds. Issues unrelated to the linux kernel should be posted on the community forum at https://forums.raspberrypi.com/
Other
10.87k stars 4.89k forks source link

UART RTS pin can get locked into an unusable state #6189

Open samskiter opened 1 month ago

samskiter commented 1 month ago

Describe the bug

Unsure where the issue lies (hardware, kernel or pyserial), but the RTS pin on the RPI seems to be able to get locked into a state that can only be recovered from by rebooting.

Steps to reproduce the behaviour

It seems that using a pyserial object and setting RS485 Mode to it sets something permanently in hardware.

After that the RTS pin doesn't operate as it should do in ser.rs485.RS485.write() - I've traced this in and checked with an oscilloscope.

The following is a fairly minimal repro, but you'll need an oscilloscope to verify:

import serial.rs485
from serial import serial_for_url
from serial.serialposix import Serial

ser_dispose = serial_for_url(
    "/dev/ttyAMA0",
    baudrate=9600,
    bytesize=8,
    parity="N",
    stopbits=1,
    rtscts=True,
    exclusive=True,
)
serial_object: Serial = ser_dispose
# !!! The following line seems to leave the hardware in an unrecoverable state - only rebooting the Pi will fix.
serial_object.rs485_mode = serial.rs485.RS485Settings(
    rts_level_for_tx=False,
    rts_level_for_rx=True,
)
ser_dispose.close()

# The following RTS settings will work but only if the above has not been executed since last reboot.
# Even if the above has been executed in a different process and killed, the following will *still* fail until next reboot
ser = serial.rs485.RS485(port="/dev/ttyAMA0", baudrate=9600, rtscts=True, parity="N")

# This will invert the behaviour of the RTS pin, but only if the above hasn't been run since last reboot.
ser.rs485_mode = serial.rs485.RS485Settings(
    rts_level_for_tx=False,
    rts_level_for_rx=True,
    loopback=False,
)

from time import sleep

sleep(3)
ser.write(b"\x00\x03\x00\x00\x01\x11")

while True:
    c = ser.read(1)
    print("read")
    print(c, end="")
    ser.write(c)

Device (s)

Raspberry Pi 4 Mod. B

System

Raspberry Pi reference 2024-03-15
Generated using pi-gen, https://github.com/RPi-Distro/pi-gen, f19ee211ddafcae300827f953d143de92a5c6624, stage4
Feb 29 2024 12:24:53 
Copyright (c) 2012 Broadcom
version f4e2138c2adc8f3a92a3a65939e458f11d7298ba (clean) (release) (start)
Linux adia 6.6.20+rpt-rpi-v8 #1 SMP PREEMPT Debian 1:6.6.20-1+rpt1 (2024-03-07) aarch64 GNU/Linux

Logs

No response

Additional context

I've traced the setRTS into serialposix.py:_update_rts_state() and printed and found all variables to be identical in the working and non working state but the hardware behaves differently - it's as if the UART is not able to have its RTS inverted - it's stuck in some kind of RS232 mode.

pelwell commented 1 month ago

Lock up in what sense? I can run your script as written and, after a 3 second wait, it outputs 6 bytes. While it does, the RTS0 line (on GPIO17) goes low, then returns high. Meanwhile, CTS0 is sitting low - I've just got a terminal emulator on the other end and haven't asked it to do any kind of flow control. After that, every key in the terminal emulator results in output from your script - "read" appears immediately, and the character appears when the next key is pressed, i.e. delayed by one keystroke.

Running the script a second time gives the same results.

How are you enabling the CTS and RTS functions on GPIOs 16 & 17? The standard overlays don't have that as an option. What does pinctrl 14-17 (or raspi-gpio get 14-17 on an older image) report?

samskiter commented 1 month ago

Note that the rs485 setting aims to invert the behaviour of RTS - this is that gets locked up - the 'setRTS' call stops working and hardware flow control seems tk take over

On Wed, 29 May 2024, 12:20 Phil Elwell, @.***> wrote:

Lock up in what sense? I can run your script as written and, after a 3 second wait, it outputs 6 bytes. While it does, the RTS0 line (on GPIO17) goes low, then returns high. Meanwhile, CTS0 is sitting low - I've just got a terminal emulator on the other end and haven't asked it to do any kind of flow control. After that, every key in the terminal emulator results in output from your script - "read" appears immediately, and the character appears when the next key is pressed, i.e. delayed by one keystroke.

How are you enabling the CTS and RTS functions on GPIOs 16 & 17? The standard overlays don't have that as an option. What does pinctrl 14-17 (or raspi-gpio get 14-17 on an older image) report?

— Reply to this email directly, view it on GitHub https://github.com/raspberrypi/linux/issues/6189#issuecomment-2137169645, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAJWOW6B6W7HO55LH3N6FALZEW2WLAVCNFSM6AAAAABILMV6VWVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDCMZXGE3DSNRUGU . You are receiving this because you authored the thread.Message ID: @.***>

pelwell commented 1 month ago

The pyserial code and documentation is careful to say that it rs485_mode only works with a limited subset of hardware and configuration settings. What makes you think this should work with inverted RTS and CTS settings?

Note that our downstream trees have a limited number of changes compared to the upstream kernel, and most of them are backports of other upstream commits. Nothing should have changed the rs485 support.

samskiter commented 1 month ago

What makes you think this should work with inverted RTS and CTS settings?

So it does work if I use the serial.rs485.RS485() class - I can confirm that with an oscilloscope

If I don't use that class - the RS485Settings don't work, but worse than that, they then prevent them working on an serial.rs485.RS485() class until I reboot the device

samskiter commented 1 month ago

Could this be related to this? https://github.com/pyserial/pyserial/issues/719

pelwell commented 1 month ago

Perhaps. You could run stty -F /dev/ttyAMA0 -a before and after the call that makes it unusable, then look for changes:

$ stty -F /dev/ttyAMA0 -a > before
$ <the bad command>
$ stty -F /dev/ttyAMA0 -a > after
$ diff before after
pelwell commented 1 month ago

Alternatively, edit the installed copy of pyserial/serial/serialposix.py, changing:

    if self._rs485_mode is not None: 
        self._set_rs485_mode(self._rs485_mode)

to:

    self._set_rs485_mode(self._rs485_mode)