Skip to content

Rev C (5", chs_5inch): cdc_acm serial path jams after one frame — proposal for an opt-in libusb transport (Linux) #1075

Description

@jeffreyjacques

Summary

On Linux, a Turing 5" revision C display (chs_5inch.dev1_rom1.88) cannot be
driven through the stock pyserial/cdc_acm path: the bulk OUT endpoint accepts
exactly one ~250-byte frame and then jams until a USB reset. The identical
endpoints work perfectly when driven with raw libusb bulk transfers. I'd like to
propose an opt-in, Linux-gated libusb transport for rev C (pyserial stays the
default everywhere), and I'm opening this first to gauge interest and preferred
shape.

This is tested on a single unit, and the proposal deliberately changes no
defaults — see the caveats and open questions at the end.

Environment

  • Display: Turing 5" rev C, ID chs_5inch.dev1_rom1.88
    • awake: 1d6b:0106 "Android", serial 20080411/dev/ttyACM1
    • asleep: 1a86:ca21 "UsbMonitor", serial CT21INCH/dev/ttyACM0
  • OS: Linux (kernel 6.x/liquorix), Python 3.13
  • Project: current main (3.10.0-109-g4df61d9), i.e. with the new
    SERIAL_OPEN_ATTEMPTS open-retry loop in lcd_comm.py
  • Kernel driver: cdc_acm binds the awake interface and creates /dev/ttyACM1

Symptom

With REVISION: C, the app opens the port fine, then _hello() reads an empty
display ID
and loops forever ("Display returned invalid or unsupported ID, try
again in 1 second"). Nothing ever draws.

What I measured

I drove the panel through the stock rev C driver (pristine origin/main, no
local changes) and instrumented every serial_write to record the exact stall
point. Key results:

  1. Open works. Stock openSerial (with the retry loop) opens /dev/ttyACM1
    on the first attempt. So the retry loop does its job — the failure is not at
    the open stage.
  2. Handshake fails. The HELLO exchange returns an empty ID; stock _hello
    retries indefinitely.
  3. Byte-level, flow control ON (stock rtscts=True): the very first 250-byte
    write drains 0 bytes (write timeout). The stock port is opened with
    hardware RTS/CTS flow control, and this CDC-ACM gadget appears not to assert
    CTS, so nothing transmits.
  4. Byte-level, flow control OFF (rtscts=False): the first 250-byte frame
    drains, but the next write stalls (Write timeout). i.e. the bulk OUT
    endpoint accepts exactly one ~250-byte frame, then jams. Recovery requires
    a USB reset / physical replug.
  5. Same endpoints over raw libusb: HELLO returns chs_5inch.dev1_rom1.88
    immediately and full-screen frames stream without limit.

So the jam is a cdc_acm data-path limit, reproduced against current
upstream, and independent of the retry loop and of flow control (I tested
both). Happy to share the standalone repro harness.

Corroboration

The OEM Windows software for these panels also bypasses the serial/COM path and
speaks raw USB bulk (WinUsb_WritePipe / LibUsbDotNet) — consistent with the
serial path being unusable for sustained transfer on this hardware.

Proposal

An opt-in, Linux-only libusb transport for rev C:

  • A config option (default off) selects it, e.g. REV_C_TRANSPORT: SERIAL | LIBUSB, read with a safe default so pyserial stays the default for everyone
    and existing rev C users are unaffected.
  • Implemented inside the rev C class (no new REVISION; routes through the
    existing factory).
  • Uses pyusb (already a dependency for TUR_USB). On Linux it detaches
    cdc_acm (detach_kernel_driver) and drives bulk OUT/IN directly.
  • Gated to Linux with graceful fallback: detach_kernel_driver is a
    Linux/usbfs capability (not supported on libusb's Windows backend), so this
    only engages on Linux; Windows/macOS keep pyserial untouched.

Caveats & open questions

  • Single unit tested (5", ROM 88). Rev C USB IDs are unreliable across sizes,
    so I can't promise other rev C variants behave identically — which is exactly
    why this is opt-in and default-off.
  • Do Windows/macOS rev C users see any equivalent stall over the COM port?
    The jam is a Linux cdc_acm behavior; Windows uses usbser.sys. If the serial
    path already works there, the libusb transport only ever needs to engage on
    Linux (as proposed).
  • Preferred opt-in mechanism? I noticed config.yaml changes are blocked by
    CI (do-not-commit-config-yaml), so I'd read the option with a default and let
    you add the documented key to the shipped template — unless you'd prefer a
    different mechanism.

Would you welcome a PR along these lines? Happy to adjust the shape (config key
name, gating, docs) to whatever fits the project.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions