A CH9102 or a CH343 needs nothing on a current Mac or Linux box. Both chips enumerate as a standard USB CDC-ACM serial device, so Linux binds cdc_acm and gives you /dev/ttyACM0, and macOS binds Apple’s own CDC driver and gives you /dev/cu.usbmodemXXXX. What goes wrong is more specific than “missing driver”: on Linux a kernel that refuses to send a BREAK on the CH343, a braille daemon that since late 2024 lists the CH343 as a braille display, and ModemManager poking the port; on the Mac a usbmodem port that runs a serial monitor fine but fails every esptool upload with Failed to write to target RAM (result was 01070000). Below: what binds 1A86:55D4 and 1A86:55D3 on each system, the kernel versions that matter, WCH’s Linux driver mirrored with its checksum, and the source behind every row. The Windows package is on the CH9102 and CH343 driver page; the CH340 has its own macOS and Linux guide.

⬇ WCH ch343 Linux driver, V2.2 (official source, mirrored)

Linux kernel 3.2 and newer · CH342, CH343, CH344, CH346, CH347, CH9101, CH9102, CH9103, CH9104, CH9105, CH9111, CH9114, CH9433 · GPL-2.0-or-later · driver file says “V2.2 On 2026.04”, repository commit of 22 Jul 2026 · checked 22 Oct 2026

Download ch343ser_linux_V2.2.zip (371 KB)

SHA-256: 937f214c52a4c419593f9a3d5584d9f852be33616b8b2d8dab5e7f4994dc2718 · the unmodified GitHub archive of commit b5515b9 (driver/, udev/, lib/, demo/) · mirror of github.com/WCHSoftGroup/ch343ser_linux, the official repository of WCH (Nanjing Qinheng Microelectronics)

macOS: the same chips are covered by WCH’s CH34xVCPDriver 2.1 package, already mirrored on the CH340 macOS and Linux page (CH34xVCPDriver_macOS_2.1.zip, SHA-256 915e7f02…208599b). Windows: the CH343SER package.

System Driver What you install Port name
Linux, any distro cdc_acm, in every kernel nothing /dev/ttyACM0
Linux, WCH vendor module ch343 V2.2, kernel ≥ 3.2 build the module above + the udev rule /dev/ttyCH343USB0
macOS 11 to 26 Apple’s CDC-ACM driver nothing /dev/cu.usbmodemXXXX
macOS 11 to 26, WCH dext CH34xVCPDriver 2.1 (dext 1.0, 26 USB IDs) pkg or dmg, then approve the extension /dev/cu.wchusbserialXXXX
OS X 10.9 to 10.15 CH34xVCPDriver.kext 1.8 (25 USB IDs, same package) pkg, restart /dev/cu.wchusbserialXXXX

What binds the chip on each system

The in-tree ch341 driver has nothing to do with these chips: its ID table in the current kernel tree lists six devices (1a86:5523, 7522, 7523, 2184:0057, 4348:5523, 9986:7523) and not one 55xx PID. The CH9102 and CH343 are claimed by cdc_acm because they present a CDC communications interface, which is also why they show up as ttyACM and not ttyUSB. WCH’s README says it in its first paragraph: the chips “are fully compliant to the Communications Device Class (CDC) standard” and “will work with a standard CDC-ACM driver”.

Diagram: what claims a CH9102 or CH343 on Linux (cdc_acm, the WCH ch343 module, brltty and ModemManager) and on macOS (Apple's CDC-ACM driver and the WCH dext)
The two binding chains and what can interfere on each. The vendor driver never wins by itself: on Linux it needs the udev unbind rule, on macOS it needs the user to approve the extension. Diagram by Tech Bench Lab from the sources listed at the end.

On the Mac the logic is the same: from Big Sur on, Apple’s CDC-ACM driver binds the chip and a /dev/cu.usbmodemXXXX node appears with no download and no approval. The WCH package adds a second driver that takes the device away from Apple’s and renames the port cu.wchusbserialXXXX; the Info.plist of its dext matches 26 USB IDs, including 55D4, 55D3 and 7523, and the legacy kext 1.8 in the same package matches 25 of them for OS X 10.9 to 10.15.

The kernel timeline that matters: BREAK on the CH343

The CH343 can send a BREAK but its CDC descriptor does not advertise the capability (CAP_BRK). Until Linux 6.7 cdc_acm sent the request anyway; Oliver Neukum’s commit 66aad7d8d3ec (7 Dec 2023) made the driver respect the descriptor, so from Linux 6.8 tcsendbreak() on a CH343 returned EOPNOTSUPP. Marc Zyngier’s fix, commit 14ae24cba291 (1 Mar 2026), adds a quirk line for 1a86:55d3 and was marked for stable. The table was read from cdc-acm.c at each tag and from the stable branches.

Kernel BREAK on CH343 (1a86:55d3) Source
up to 6.7 works (capability not checked) acm_tty_break_ctl() in v6.7 has no CAP_BRK test
6.8 to 6.19 as released refused, EOPNOTSUPP 66aad7d8d3ec, first in v6.8
7.0 and newer works again (MISSING_CAP_BRK quirk) 14ae24cba291, committed 11 Mar 2026
6.19.9 · 6.18.19 works (backport of 19 Mar 2026) stable branches linux-6.19.y, linux-6.18.y
6.12.78 · 6.6.130 · 6.1.167 works (backport of 25 Mar 2026) stable branches linux-6.12.y, linux-6.6.y, linux-6.1.y
CH9102 (1a86:55d4), any kernel no quirk line only 55d3 is in acm_ids[]; if your CH9102 does not advertise CAP_BRK either, BREAK stays refused from 6.8 on
The kernel patch 'usb: cdc-acm: Restore CAP_BRK functionnality to CH343' as served by GitHub, showing the MISSING_CAP_BRK quirk and the new 1a86:55d3 line
The patch as served by github.com/torvalds/linux, captured 22 Oct 2026: six added lines, one of them the 1a86:55d3 entry with the comment “CH343 supports CAP_BRK, but doesn’t advertise it”.

This only matters to software that calls tcsendbreak(), such as console servers (the use case in the commit message) and RS-485 tooling (issue 74, 9 Oct 2026, asks for BREAK reception in the vendor module too). esptool, PlatformIO, the Arduino IDE and serial monitors work on every kernel in the table.

Linux: the two commands, and when the vendor module is worth it

lsusb | grep -i 1a86          # ID 1a86:55d4 QinHeng Electronics USB Single Serial
dmesg | tail                  # cdc_acm 1-2:1.0: ttyACM0: USB ACM device
ls -l /dev/ttyACM*            # crw-rw---- 1 root dialout ...   (uucp on Arch)

If the node is there and you cannot open it, you are not in the group that owns it: sudo usermod -aG dialout $USER (Debian, Ubuntu, Fedora) or uucp (Arch), then log out and in. That was the whole story in Arduino forum thread 1334478 (Arch, CH343 on an ESP32-S3 board).

The vendor module is worth building only for what cdc_acm cannot do, which WCH lists as hardware flow control, the RS-485 TNOW direction signal, the GPIO pins of the CH9102 and CH344, and the USB parameter configuration tool in demo/param_config. From the README in the archive:

unzip ch343ser_linux_V2.2.zip && cd ch343ser_linux-*/driver
make                                   # needs the headers for your kernel
sudo make install                      # insmod + copy to /lib/modules/.../usb/serial + depmod
sudo cp ../udev/99-ch34x.rules /etc/udev/rules.d/
sudo udevadm control -R                # then unplug and replug the board
ls /dev/ttyCH343USB*

The udev rule is not optional: both drivers match the device and cdc_acm is already loaded, so without it you keep getting ttyACM0 and never ttyCH343USB0 (issue 11). The rule from pull request 27 (2 Aug 2024) writes the device to /sys/bus/usb/drivers/cdc_acm/unbind and then to usb_ch343/bind, and sets the node to MODE="0660", GROUP="plugdev", so you need that group too. Under Secure Boot the unsigned module will not load; issue 70 has a DKMS plus MOK recipe. And the nodes are named ttyCH343USB* on purpose (issue 14, open since 2022), so tools that scan only ttyUSB* and ttyACM* will not list them.

The two Linux interceptors: brltty 6.7 and ModemManager

If the port appears in dmesg and vanishes a second later, or is busy for a few seconds after plugging, look at these two before touching drivers.

Daemon What it does to the chip Where that comes from Fix
brltty 6.7 and newer lists 1A86:55D3 (CH343) as a braille display and grabs the device; 55D4 (CH9102) is not listed Programs/usb_devices.c: entry “HIMS [eMotion (legacy)]”, added 4 Oct 2024, first release 6.7 (15 Oct 2024), absent in 6.6; generic IDs go into usb-generic.rules (Autostart/Udev/Makefile.in) grep -rl 55d3 /usr/lib/udev/rules.d/ /lib/udev/rules.d/; if it hits, sudo apt remove brltty or mask that rule
ModemManager probes a cdc_acm port for AT commands when the interface is class 2 / subclass 2 / protocol 1 ModemManager filter documentation, rule MM_FILTER_RULE_TTY_ACM_INTERFACE udev rule ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="55d4", ENV{ID_MM_DEVICE_IGNORE}="1", then udevadm control --reload; check the byte with lsusb -v -d 1a86: | grep bInterfaceProtocol

Distributions with the brltty that knows the CH343: Ubuntu 25.04, 25.10 and 26.04 (6.7), Ubuntu 26.10 (6.9.1), Debian 13 (6.7) and Debian 14 (6.9.1). Ubuntu 24.04 (6.6), 22.04 (6.4) and Debian 12 (6.5) do not have the entry. Whether the rule fires also depends on which generated rules file your package installs; the grep above answers that for your machine.

macOS: two ports, two behaviours

With nothing installed, every macOS from Big Sur on gives a CH9102 a cu.usbmodem port, and a serial monitor on it works. Uploading firmware to an ESP32 through that port is where the reports pile up, all with the same esptool line:

A fatal error occurred: Failed to write to target RAM (result was 01070000: Operation timed out)

M5Stack’s M5EPD issue 47 (Big Sur, Nov 2021) traced it to a CH9102 on a board whose case says CP2104; the same user hit it on an M1 with Monterey (STAMP-C3 issue 1); PlatformIO threads 24575 and 26229 (macOS 12.2.1) show the whole pattern: cu.usbmodem52D60049421 fails, and after installing WCH’s package the new cu.wchusbserial52D60049421 uploads. As a PlatformIO regular put it, the CDC interface “is supported out-of-the-box on macOS. But for reasons unknown, it works for the serial monitor but does not work for uploading firmware.” Rule on a Mac: monitor on either port, flash on wchusbserial.

Installing 2.1: run the pkg (or drag the app from the dmg on a Mac without Rosetta), open the CH34xVCPDriver app, click Install, approve the extension, replug. On macOS 15.6 and newer the approval lives in System Settings → General → Login Items & Extensions → Driver Extensions, which is what worked in ch34xser_macos issue 47 (macOS 26). The message “system extension(s) … that need to be updated by the developer” (issue 38, macOS 15.6.1) means an older package; 2.1 is current, and the Homebrew cask wch-ch34x-usb-serial-driver installs 2.1 too. Uninstall from the same app and the port returns to cu.usbmodem.

Common problems, with the thread behind each

What you see Cause Fix Thread
Linux: ttyACM0 appears, ttyCH343USB0 never does, after building the module cdc_acm claimed the interface first; udev rule missing or not reloaded copy 99-ch34x.rules, udevadm control -R, replug; rmmod cdc_acm for a quick test ch343ser_linux 11
Linux: port exists, Arduino IDE 2 or PlatformIO cannot open it group ownership (dialout, uucp, or plugdev with the WCH rule) add yourself to the group, log out and in Arduino forum 1334478
Linux: tcsendbreak on a CH343 fails with “Operation not supported” kernel 6.8 to 6.19 without the backport update to 7.0, 6.19.9, 6.18.19, 6.12.78, 6.6.130 or 6.1.167; or use the WCH module, which has its own break_ctl commit 14ae24cba291
macOS: monitor works on cu.usbmodem, esptool says Failed to write to target RAM (01070000) Apple’s CDC-ACM driver and the ESP32 bootloader handshake install CH34xVCPDriver 2.1, flash on cu.wchusbserial PlatformIO 26229
macOS, Apple Silicon: RTS/DTR do not move on the wchusbserial port, ioctl(TIOCMSET) returns ENOTTY reported against dext 2.0 on macOS 26.5.1; tools that drive auto-reset through RTS/DTR are affected open since 18 Jun 2026; WCH points to its WCHSerialPort app; press BOOT by hand, or monitor on Apple’s port ch34xser_macos 50
macOS: CH343 board shows no device at all in System Information cable or board power, not the driver; a dual-port board also exposes a native “USB JTAG/serial debug unit” another cable, hold BOOT at power-up, check System Information before installing anything ch34xser_macos 41

FAQ

Do I need the WCH driver on Linux for an ESP32 board with a CH9102? No. cdc_acm handles upload and monitor, including the DTR/RTS reset. Build the module only for RS-485 direction control, GPIO or hardware flow control.

Why is my CH9102 /dev/ttyACM0 on one machine and /dev/ttyCH343USB0 on another? The second machine has the WCH module and its udev rule; the first has the kernel default. Both work for serial, only the node name differs.

Related guides

Sources