git: b4daeded66b5 - main - usb: umass: add SCSIEJECT quirk and fix RTW8821CU_CD (USB mode switch)

From: Bjoern A. Zeeb <bz_at_FreeBSD.org>
Date: Mon, 09 Mar 2026 14:35:48 UTC
The branch main has been updated by bz:

URL: https://cgit.FreeBSD.org/src/commit/?id=b4daeded66b5e950ed8e618d66915b863c2414b1

commit b4daeded66b5e950ed8e618d66915b863c2414b1
Author:     Bjoern A. Zeeb <bz@FreeBSD.org>
AuthorDate: 2026-01-26 13:19:37 +0000
Commit:     Bjoern A. Zeeb <bz@FreeBSD.org>
CommitDate: 2026-03-09 14:35:31 +0000

    usb: umass: add SCSIEJECT quirk and fix RTW8821CU_CD (USB mode switch)
    
    Several Realtek (and lots other) USB dongles present themselves as
    CDROM device first.  Upon eject they do a mode switch and suddenly
    are a different kind of device (sometimes even with different IDs),
    e.g., a wireless dongle.
    
    In order to avoid the CDROM stage and rather than adding the quirk
    handling to more drivers, add support to umass and if enabled
    automatically eject the "CDROM" to make it the real device.
    
    Longer-term some other drivers could stop using their hand-rolled
    support for this.  It is unclear as-to how much we need the list of
    (eject) quirks from u3g here, or if these are very specific to that
    kind of devices.
    
    Sponsored by:   The FreeBSD Foundation
    Fixes:          b3b6a959c85a, 9c0cce328363
    Reviewed by:    imp
    Differential Revision: https://reviews.freebsd.org/D54901
---
 sys/dev/usb/quirk/usb_quirk.c |  2 +-
 sys/dev/usb/storage/umass.c   | 57 ++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 57 insertions(+), 2 deletions(-)

diff --git a/sys/dev/usb/quirk/usb_quirk.c b/sys/dev/usb/quirk/usb_quirk.c
index 04441b2a344b..303f76f37fb0 100644
--- a/sys/dev/usb/quirk/usb_quirk.c
+++ b/sys/dev/usb/quirk/usb_quirk.c
@@ -532,7 +532,7 @@ static struct usb_quirk_entry usb_quirks[USB_DEV_QUIRKS_MAX] = {
 	    UQ_MSC_NO_INQUIRY, UQ_CFG_INDEX_0),
 	USB_QUIRK(SMART2, G2MEMKEY, UQ_MSC_NO_INQUIRY),
 	USB_QUIRK_REV(RALINK, RT_STOR, 0x0001, 0x0001, UQ_MSC_IGNORE),
-	USB_QUIRK(REALTEK, RTW8821CU_CD, UQ_MSC_IGNORE),
+	USB_QUIRK(REALTEK, RTW8821CU_CD, UQ_MSC_EJECT_SCSIEJECT),
 	/* Non-standard USB MIDI devices */
 	USB_QUIRK(ROLAND, UM1, UQ_AU_VENDOR_CLASS),
 	USB_QUIRK(ROLAND, SC8850, UQ_AU_VENDOR_CLASS),
diff --git a/sys/dev/usb/storage/umass.c b/sys/dev/usb/storage/umass.c
index cacf4ddf8f16..0ee6ea992fa7 100644
--- a/sys/dev/usb/storage/umass.c
+++ b/sys/dev/usb/storage/umass.c
@@ -115,6 +115,7 @@
 #include <sys/sx.h>
 #include <sys/unistd.h>
 #include <sys/callout.h>
+#include <sys/eventhandler.h>
 #include <sys/malloc.h>
 #include <sys/priv.h>
 
@@ -124,6 +125,7 @@
 #include "usbdevs.h"
 
 #include <dev/usb/quirk/usb_quirk.h>
+#include <dev/usb/usb_msctest.h>
 
 #include <cam/cam.h>
 #include <cam/cam_ccb.h>
@@ -705,6 +707,59 @@ static const uint8_t fake_inq_data[SHORT_INQUIRY_LENGTH] = {
 #define	UFI_COMMAND_LENGTH	12	/* UFI commands are always 12 bytes */
 #define	ATAPI_COMMAND_LENGTH	12	/* ATAPI commands are always 12 bytes */
 
+static void
+umass_autoinst_eject_quirks(void *arg __unused, struct usb_device *udev,
+    struct usb_attach_arg *uaa)
+{
+	struct usb_interface *iface;
+	struct usb_interface_descriptor *id;
+
+	if (uaa->dev_state != UAA_DEV_READY)
+		return;
+
+	iface = usbd_get_iface(udev, 0);
+	if (iface == NULL)
+		return;
+
+	id = iface->idesc;
+	if (id == NULL || id->bInterfaceClass != UICLASS_MASS)
+		return;
+
+	if (usb_test_quirk(uaa, UQ_MSC_EJECT_SCSIEJECT)) {
+		int error;
+
+		error = usb_msc_eject(uaa->device, 0, MSC_EJECT_STOPUNIT);
+		if (error == 0)
+			uaa->dev_state = UAA_DEV_EJECTING;
+		else
+			printf("UMASS failed to eject by SCSI eject STOPUNIT "
+			    "command based on quirk: %d\n", error);
+	}
+}
+
+static eventhandler_tag umass_drv_evh_tag;
+
+static int
+umass_driver_evh(struct module *mod, int what, void *arg)
+{
+
+	switch (what) {
+	case MOD_LOAD:
+		umass_drv_evh_tag = EVENTHANDLER_REGISTER(usb_dev_configured,
+		    umass_autoinst_eject_quirks, NULL, EVENTHANDLER_PRI_ANY);
+		break;
+	case MOD_UNLOAD:
+		if (umass_drv_evh_tag != NULL)
+			EVENTHANDLER_DEREGISTER(usb_dev_configured,
+			    umass_drv_evh_tag);
+		break;
+	default:
+		return (EOPNOTSUPP);
+	}
+
+	return (0);
+}
+
 static device_method_t umass_methods[] = {
 	/* Device interface */
 	DEVMETHOD(device_probe, umass_probe),
@@ -725,7 +780,7 @@ static const STRUCT_USB_HOST_ID __used umass_devs[] = {
 	{USB_IFACE_CLASS(UICLASS_MASS),},
 };
 
-DRIVER_MODULE(umass, uhub, umass_driver, NULL, NULL);
+DRIVER_MODULE(umass, uhub, umass_driver, umass_driver_evh, NULL);
 MODULE_DEPEND(umass, usb, 1, 1, 1);
 MODULE_DEPEND(umass, cam, 1, 1, 1);
 MODULE_VERSION(umass, 1);