On Wed, Jun 11, 2025 at 07:32:57AM -0700, [email protected] wrote:
>
> >Synopsis:    uchcom(4) seems to not flush received data
> >Category:    kernel
> >Environment:
>       System      : OpenBSD 7.7
>       Details     : OpenBSD 7.7 (GENERIC.MP) #625: Sun Apr 13 08:30:20 MDT 
> 2025
>                        
> [email protected]:/usr/src/sys/arch/amd64/compile/GENERIC.MP
>
>       Architecture: OpenBSD.amd64
>       Machine     : amd64
> >Description:
>       i have a device which presents its serial line as a CH341:
>
>               uchcom0 at uhub1 port 2 configuration 1 interface 0 "QinHeng 
> Electronics USB2.0-Serial" rev 1.10/2.54 addr 5
>               uchcom0: CH341
>               ucom2 at uchcom0: usb0.2.00002.0
>
>       the other end of it is attached to a linux SBC, running a normal 
> getty/login/shell.
>
>       when i type, and the remote sends something, the information is not
>       flushed immediately and visible, but it seems somehow still stuck in a 
> buffer
>       somewhere. for example, i would normally expect:
>
>               mischief@megrez:~$     # press enter
>               mischief@megrez:~$     # full shell prompt line appears
>
>       but on this serial adapter, instead i get something like:
>
>               mischief@megrez:~$     # press enter
>               mischief@megr          # line from remote is not fully flushed, 
> press enter again
>
>               mischief@megrez:~$     # the previous line now fully appears
>               mischie                # subsequent line not fully flushed again
>
>       this also happens for non-newlines, such as just typing regular
>       characters at the shell. for example, if i just type 'a', nothing 
> appears until
>       i type several 'a' in a row.
>
>       it happens with both cu(1) and picocom from packages.
>
>       in testing, the 'flush' of data seems to happen at exactly a 32-byte
>       boundary, e.g., if i type some characters, when they finally appear, if 
> i type
>       exactly 32 more, it is then flushed and appears, which would make sense 
> as the
>       size of a buffer for the data. however, the same device does not 
> exhibit the
>       same behavior when using cu or picocom under linux, so my suspicion was 
> that
>       this is an issue with the uchcom(4) driver, and not a problem with the 
> device
>       itself or cu/etc.
>
>       the device in question is listed below from usbdevs, with vid/did 
> 1a86:7523.
>
> >How-To-Repeat:
>       cu -s 115200 -l /dev/cuaU2, try to type
> >Fix:
>       unsure, but the linux driver may reveal a clue.


When I added support for the CH343, I didn't touch any of the CH341/CH340
code paths.  I also tested my CH341 with several SBCs, unfortunately I wasn't
able to reproduce your issue.

uchcom0 at uhub1 port 1 configuration 1 interface 0 "QinHeng Electronics 
USB2.0-Serial" rev 1.10/2.63 addr 5
uchcom0: CH341
ucom0 at uchcom0: usb1.0.00001.0

$ usbdevs | grep 7523
addr 05: 1a86:7523 QinHeng Electronics, USB2.0-Serial

Since the CH340/CH341 seem to share many common parts with the CH343,
could you try the diff below to see if it fixes your issue?  Thanks.

Index: sys/dev/usb/uchcom.c
===================================================================
RCS file: /cvs/src/sys/dev/usb/uchcom.c,v
diff -u -p -u -p -r1.37 uchcom.c
--- sys/dev/usb/uchcom.c        22 Oct 2024 21:50:02 -0000      1.37
+++ sys/dev/usb/uchcom.c        11 Jul 2025 02:14:29 -0000
@@ -116,14 +116,8 @@ int        uchcomdebug = 0;
 #define UCHCOM_T               0x08
 #define UCHCOM_CL              0x04
 #define UCHCOM_CT              0x80
-/*
- * XXX - these magic numbers come from Linux (drivers/usb/serial/ch341.c).
- * The manufacturer was unresponsive when asked for documentation.
- */
-#define UCHCOM_RESET_VALUE     0x501F  /* line mode? */
-#define UCHCOM_RESET_INDEX     0xD90A  /* baud rate? */

-#define UCHCOMOBUFSIZE 256
+#define UCHCOMOBUFSIZE 256

 #define UCHCOM_TYPE_CH343      1

@@ -158,29 +152,6 @@ struct uchcom_endpoints {
        int             ep_intr_size;
 };

-struct uchcom_divider {
-       uint8_t         dv_prescaler;
-       uint8_t         dv_div;
-       uint8_t         dv_mod;
-};
-
-struct uchcom_divider_record {
-       uint32_t                dvr_high;
-       uint32_t                dvr_low;
-       uint32_t                dvr_base_clock;
-       struct uchcom_divider   dvr_divider;
-};
-
-static const struct uchcom_divider_record dividers[] =
-{
-       {  307200, 307200, UCHCOM_BASE_UNKNOWN, { 7, 0xD9, 0 } },
-       {  921600, 921600, UCHCOM_BASE_UNKNOWN, { 7, 0xF3, 0 } },
-       { 2999999,  23530,             6000000, { 3,    0, 0 } },
-       {   23529,   2942,              750000, { 2,    0, 0 } },
-       {    2941,    368,               93750, { 1,    0, 0 } },
-       {     367,      1,               11719, { 0,    0, 0 } },
-};
-
 void           uchcom_get_status(void *, int, u_char *, u_char *);
 void           uchcom_set(void *, int, int, int);
 int            uchcom_param(void *, int, struct termios *);
@@ -211,16 +182,12 @@ int               uchcom_update_status(struct uchcom_
 int            uchcom_set_dtrrts(struct uchcom_softc *, int, int);
 int            uchcom_set_break(struct uchcom_softc *, int);
 int            uchcom_set_break_ch343(struct uchcom_softc *, int);
-void           uchcom_calc_baudrate_ch343(uint32_t, uint8_t *, uint8_t *);
-int            uchcom_calc_divider_settings(struct uchcom_divider *, uint32_t);
-int            uchcom_set_dte_rate_ch343(struct uchcom_softc *, uint32_t,
-                   uint16_t);
-int            uchcom_set_dte_rate(struct uchcom_softc *, uint32_t);
+void           uchcom_calc_baudrate(struct uchcom_softc *, uint32_t, uint8_t *,
+                   uint8_t *);
+int            uchcom_set_dte_rate(struct uchcom_softc *, uint32_t, uint16_t);
 uint16_t       uchcom_set_line_control(struct uchcom_softc *, tcflag_t,
                    uint16_t *);
 int            uchcom_clear_chip(struct uchcom_softc *);
-int            uchcom_reset_chip(struct uchcom_softc *);
-int            uchcom_setup_comm(struct uchcom_softc *);
 int            uchcom_setup_intr_pipe(struct uchcom_softc *);


@@ -576,9 +543,6 @@ uchcom_update_version(struct uchcom_soft
 void
 uchcom_convert_status(struct uchcom_softc *sc, uint8_t cur)
 {
-       sc->sc_dtr = !(cur & UCHCOM_DTR_MASK);
-       sc->sc_rts = !(cur & UCHCOM_RTS_MASK);
-
        cur = ~cur & 0x0F;
        sc->sc_msr = (cur << 4) | ((sc->sc_msr >> 4) ^ cur);
 }
@@ -595,6 +559,10 @@ uchcom_update_status(struct uchcom_softc
                       sc->sc_dev.dv_xname, usbd_errstr(err));
                return EIO;
        }
+
+       sc->sc_dtr = !(cur & UCHCOM_DTR_MASK);
+       sc->sc_rts = !(cur & UCHCOM_RTS_MASK);
+
        uchcom_convert_status(sc, cur);

        return 0;
@@ -666,11 +634,12 @@ uchcom_set_break_ch343(struct uchcom_sof
 }

 void
-uchcom_calc_baudrate_ch343(uint32_t rate, uint8_t *divisor, uint8_t *factor)
+uchcom_calc_baudrate(struct uchcom_softc *sc, uint32_t rate, uint8_t *divisor,
+    uint8_t *factor)
 {
        uint32_t clk = 12000000;

-       if (rate >= 256000)
+       if (rate >= 921600 && rate != 1500000)
                *divisor = 7;
        else if (rate > 23529) {
                clk /= 2;
@@ -686,56 +655,21 @@ uchcom_calc_baudrate_ch343(uint32_t rate
                *divisor = 0;
        }

-       *factor = 256 - clk / rate;
-}
-
-int
-uchcom_calc_divider_settings(struct uchcom_divider *dp, uint32_t rate)
-{
-       int i;
-       const struct uchcom_divider_record *rp;
-       uint32_t div, rem, mod;
-
-       /* find record */
-       for (i=0; i<nitems(dividers); i++) {
-               if (dividers[i].dvr_high >= rate &&
-                   dividers[i].dvr_low <= rate) {
-                       rp = &dividers[i];
-                       goto found;
-               }
-       }
-       return -1;
-
-found:
-       dp->dv_prescaler = rp->dvr_divider.dv_prescaler;
-       if (rp->dvr_base_clock == UCHCOM_BASE_UNKNOWN)
-               dp->dv_div = rp->dvr_divider.dv_div;
-       else {
-               div = rp->dvr_base_clock / rate;
-               rem = rp->dvr_base_clock % rate;
-               if (div==0 || div>=0xFF)
-                       return -1;
-               if ((rem<<1) >= rate)
-                       div += 1;
-               dp->dv_div = (uint8_t)-div;
-       }
-
-       mod = UCHCOM_BPS_MOD_BASE/rate + UCHCOM_BPS_MOD_BASE_OFS;
-       mod = mod + mod/2;
-
-       dp->dv_mod = mod / 0x100;
-
-       return 0;
+       if (rate == 921600 && sc->sc_type != UCHCOM_TYPE_CH343)
+               *factor = 243;
+       else
+               *factor = 256 - clk / rate;
 }

 int
-uchcom_set_dte_rate_ch343(struct uchcom_softc *sc, uint32_t rate, uint16_t val)
+uchcom_set_dte_rate(struct uchcom_softc *sc, uint32_t rate, uint16_t val)
 {
        usbd_status err;
        uint16_t idx;
        uint8_t factor, div;

-       uchcom_calc_baudrate_ch343(rate, &div, &factor);
+       uchcom_calc_baudrate(sc, rate, &div, &factor);
+       div |= (sc->sc_type != UCHCOM_TYPE_CH343) ? 0x80 : 0;
        idx = (factor << 8) | div;

        err = uchcom_generic_control_out(sc, UCHCOM_REQ_SET_BAUDRATE, val, idx);
@@ -748,34 +682,10 @@ uchcom_set_dte_rate_ch343(struct uchcom_
        return 0;
 }

-int
-uchcom_set_dte_rate(struct uchcom_softc *sc, uint32_t rate)
-{
-       usbd_status err;
-       struct uchcom_divider dv;
-
-       if (uchcom_calc_divider_settings(&dv, rate))
-               return EINVAL;
-
-       if ((err = uchcom_write_reg(sc,
-                            UCHCOM_REG_BPS_PRE, dv.dv_prescaler,
-                            UCHCOM_REG_BPS_DIV, dv.dv_div)) ||
-           (err = uchcom_write_reg(sc,
-                            UCHCOM_REG_BPS_MOD, dv.dv_mod,
-                            UCHCOM_REG_BPS_PAD, 0))) {
-               printf("%s: cannot set DTE rate: %s\n",
-                      sc->sc_dev.dv_xname, usbd_errstr(err));
-               return EIO;
-       }
-
-       return 0;
-}
-
 uint16_t
 uchcom_set_line_control(struct uchcom_softc *sc, tcflag_t cflag, uint16_t *val)
 {
-       usbd_status err;
-       uint8_t lcr = 0, lcr2 = 0;
+       uint8_t lcr = 0;

        if (sc->sc_release == UCHCOM_REV_CH340) {
                /*
@@ -797,16 +707,6 @@ uchcom_set_line_control(struct uchcom_so
                return 0;
        }

-       if (sc->sc_type != UCHCOM_TYPE_CH343) {
-               err = uchcom_read_reg(sc, UCHCOM_REG_LCR, &lcr, UCHCOM_REG_LCR2,
-                   &lcr2);
-               if (err) {
-                       printf("%s: cannot get LCR: %s\n",
-                           sc->sc_dev.dv_xname, usbd_errstr(err));
-                       return EIO;
-               }
-       }
-
        lcr = UCHCOM_LCR_RXE | UCHCOM_LCR_TXE;

        switch (ISSET(cflag, CSIZE)) {
@@ -834,16 +734,8 @@ uchcom_set_line_control(struct uchcom_so
                lcr |= UCHCOM_LCR_STOPB;
        }

-       if (sc->sc_type != UCHCOM_TYPE_CH343) {
-               err = uchcom_write_reg(sc, UCHCOM_REG_LCR, lcr, UCHCOM_REG_LCR2,
-                   lcr2);
-               if (err) {
-                       printf("%s: cannot set LCR: %s\n",
-                           sc->sc_dev.dv_xname, usbd_errstr(err));
-                       return EIO;
-               }
-       } else
-               *val = UCHCOM_T | UCHCOM_CL | UCHCOM_CT | lcr << 8;
+       *val = UCHCOM_T | UCHCOM_CL | UCHCOM_CT | lcr << 8;
+       *val |= (sc->sc_type != UCHCOM_TYPE_CH343) ? 0x10 : 0;

        return 0;
 }
@@ -865,59 +757,6 @@ uchcom_clear_chip(struct uchcom_softc *s
 }

 int
-uchcom_reset_chip(struct uchcom_softc *sc)
-{
-       usbd_status err;
-
-       DPRINTF(("%s: reset\n", sc->sc_dev.dv_xname));
-
-       err = uchcom_generic_control_out(sc, UCHCOM_REQ_RESET,
-                                        UCHCOM_RESET_VALUE,
-                                        UCHCOM_RESET_INDEX);
-       if (err)
-               goto failed;
-
-       return 0;
-
-failed:
-       printf("%s: cannot reset: %s\n",
-              sc->sc_dev.dv_xname, usbd_errstr(err));
-       return EIO;
-}
-
-int
-uchcom_setup_comm(struct uchcom_softc *sc)
-{
-       int ret;
-
-       ret = uchcom_clear_chip(sc);
-       if (ret)
-               return ret;
-
-       ret = uchcom_set_dte_rate(sc, TTYDEF_SPEED);
-       if (ret)
-               return ret;
-
-       ret = uchcom_set_line_control(sc, CS8, 0);
-       if (ret)
-               return ret;
-
-       ret = uchcom_update_status(sc);
-       if (ret)
-               return ret;
-
-       ret = uchcom_reset_chip(sc);
-       if (ret)
-               return ret;
-
-       ret = uchcom_set_dte_rate(sc, TTYDEF_SPEED); /* XXX */
-       if (ret)
-               return ret;
-
-       return 0;
-}
-
-int
 uchcom_setup_intr_pipe(struct uchcom_softc *sc)
 {
        usbd_status err;
@@ -1008,14 +847,16 @@ uchcom_param(void *arg, int portno, stru
        if (usbd_is_dying(sc->sc_udev))
                return 0;

+       if (t->c_ospeed <= 0 ||
+           (t->c_ospeed > 921600 && sc->sc_type != UCHCOM_TYPE_CH343) ||
+           (t->c_ospeed > 6000000 && sc->sc_type == UCHCOM_TYPE_CH343))
+               return EINVAL;
+
        ret = uchcom_set_line_control(sc, t->c_cflag, &val);
        if (ret)
                return ret;

-       if (sc->sc_type == UCHCOM_TYPE_CH343)
-               ret = uchcom_set_dte_rate_ch343(sc, t->c_ospeed, val);
-       else
-               ret = uchcom_set_dte_rate(sc, t->c_ospeed);
+       ret = uchcom_set_dte_rate(sc, t->c_ospeed, val);
        if (ret)
                return ret;

@@ -1025,8 +866,8 @@ uchcom_param(void *arg, int portno, stru
 int
 uchcom_open(void *arg, int portno)
 {
-       int ret;
        struct uchcom_softc *sc = arg;
+       int ret;

        if (usbd_is_dying(sc->sc_udev))
                return EIO;
@@ -1039,10 +880,13 @@ uchcom_open(void *arg, int portno)
        if (ret)
                return ret;

-       if (sc->sc_type == UCHCOM_TYPE_CH343)
-               ret = uchcom_update_status(sc);
-       else
-               ret = uchcom_setup_comm(sc);
+       if (sc->sc_type != UCHCOM_TYPE_CH343) {
+               ret = uchcom_clear_chip(sc);
+               if (ret)
+                       return ret;
+       }
+
+       ret = uchcom_update_status(sc);
        if (ret)
                return ret;

Reply via email to