From nobody Mon Sep 15 00:05:50 2025 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 261DAC3DA78 for ; Tue, 17 Jan 2023 15:18:00 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S233886AbjAQPR4 convert rfc822-to-8bit (ORCPT ); Tue, 17 Jan 2023 10:17:56 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:53954 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S233883AbjAQPRc (ORCPT ); Tue, 17 Jan 2023 10:17:32 -0500 Received: from relay9-d.mail.gandi.net (relay9-d.mail.gandi.net [IPv6:2001:4b98:dc4:8::229]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id E4BC841B4A; Tue, 17 Jan 2023 07:17:26 -0800 (PST) Received: (Authenticated sender: hadess@hadess.net) by mail.gandi.net (Postfix) with ESMTPSA id ACAE5FF80C; Tue, 17 Jan 2023 15:17:23 +0000 (UTC) Message-ID: Subject: [RFC] USB: core: Add wireless_status sysfs attribute From: Bastien Nocera To: Greg Kroah-Hartman , "Rafael J. Wysocki" , Benjamin Tissoires Cc: linux-kernel@vger.kernel.org, linux-usb@vger.kernel.org Date: Tue, 17 Jan 2023 16:17:23 +0100 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable User-Agent: Evolution 3.46.3 (3.46.3-1.fc37) MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Hey, TLDR: new sysfs attribute that makes it possible to leave receivers for wireless headsets plugged in. At the USB level, or at the base driver level? Longer version: I started working on implementing support for some wireless headsets that use USB receivers to communicate to the headset itself. The USB receivers have multiple interfaces, and independent drivers for each, as is wont to do for USB devices. There's usually a HID interface to do the custom stuff (LEDs, battery status, connection status, etc.) and a standard audio class interface. Those drivers don't know anything about each other, and getting them to talk to each other would be rather complicated. Additionally the audio interface is still somewhat functional when the headset is disconnected. In the end, I came up with this new sysfs attribute that would make it possible for user-space (PulseAudio or Pipewire) to know whether the receiver is plugged in or not. That allows user-space to not show the battery information for the device (rather than 0 percent), not offer the headset as an output, and potentially automatically switch to it when the headset is powered on. The question is whether this should be a USB sysfs attribute, or one at the base driver level. Example implementation of the USB sysfs attribute itself below. I have a patch for a USB API as well, but I'm having some problems creating deferred work on a soft irq. Cheers ---- Add a wireless_status sysfs attribute to USB devices to keep track of whether a USB device that uses a receiver/emitter combo has its emitter connected or disconnected. By default, the USB device will declare not to use a receiver/emitter. Signed-off-by: Bastien Nocera --- =C2=A0Documentation/ABI/testing/sysfs-bus-usb | 11 ++++++ =C2=A0drivers/usb/core/sysfs.c=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2= =A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 | 50 +++++++++++++++++++++++++ =C2=A0drivers/usb/core/usb.h=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2= =A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 |=C2=A0 1 + =C2=A0include/linux/usb.h=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0= =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 |= =C2=A0 9 +++++ =C2=A04 files changed, 71 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-bus-usb b/Documentation/ABI/te= sting/sysfs-bus-usb index 568103d3376e..23ba756d40f7 100644 --- a/Documentation/ABI/testing/sysfs-bus-usb +++ b/Documentation/ABI/testing/sysfs-bus-usb @@ -166,6 +166,17 @@ Description: =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2= =A0=C2=A0=C2=A0=C2=A0The file will be present for all speeds of USB devices= , and will =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2= =A0=C2=A0=C2=A0=C2=A0always read "no" for USB 1.1 and USB 2.0 devices. =C2=A0 +What:=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0/sys/bus/= usb/devices/.../wireless_status +Date:=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0December = 2022 +Contact:=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0Bastien Nocera +Description: +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0= =C2=A0=C2=A0=C2=A0Some USB devices use a small USB receiver coupled with a = larger +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0= =C2=A0=C2=A0=C2=A0wireless device, usually communicating using proprietary +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0= =C2=A0=C2=A0=C2=A0wireless protocols. This attribute will read either "conn= ected" +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0= =C2=A0=C2=A0=C2=A0or "disconnected" depending on whether the emitter is tur= ned on, +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0= =C2=A0=C2=A0=C2=A0in range and connected. If the device does not use a rece= iver/ +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0= =C2=A0=C2=A0=C2=A0emitter combo, then this attribute will not exist. + =C2=A0What:=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0/sys= /bus/usb/devices/...//port =C2=A0Date:=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0Augu= st 2012 =C2=A0Contact:=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0Lan Tianyu diff --git a/drivers/usb/core/sysfs.c b/drivers/usb/core/sysfs.c index 631574718d8a..6e963fc9ed73 100644 --- a/drivers/usb/core/sysfs.c +++ b/drivers/usb/core/sysfs.c @@ -849,12 +849,62 @@ static const struct attribute_group dev_string_attr_g= rp =3D { =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0.is_visible =3D=C2=A0=C2=A0= =C2=A0dev_string_attrs_are_visible, =C2=A0}; =C2=A0 +static ssize_t wireless_status_show(struct device *dev, +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0= =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2= =A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 struct device_att= ribute *attr, char *buf) +{ +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0struct usb_device *udev; + +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0udev =3D to_usb_device(dev); +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0if (udev->wireless_status =3D=3D= USB_WIRELESS_STATUS_DISCONNECTED) +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0= =C2=A0=C2=A0=C2=A0return sysfs_emit(buf, "%s\n", "disconnected"); +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0return sysfs_emit(buf, "%s\n", "= connected"); +} +static DEVICE_ATTR_RO(wireless_status); + +static struct attribute *dev_wireless_status_attrs[] =3D { +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0&dev_attr_wireless_status.attr, +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0NULL +}; + +static umode_t dev_wireless_status_attr_is_visible(struct kobject *kobj, +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0= =C2=A0=C2=A0=C2=A0struct attribute *a, int n) +{ +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0struct device *dev =3D kobj_to_d= ev(kobj); +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0struct usb_device *udev =3D to_u= sb_device(dev); + +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0if (a !=3D &dev_attr_wireless_st= atus.attr || +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 udev->wireles= s_status !=3D USB_WIRELESS_STATUS_NA) +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0= =C2=A0=C2=A0=C2=A0return a->mode; +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0return 0; +} + +static const struct attribute_group dev_wireless_status_attr_grp =3D { +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0.attrs =3D=C2=A0=C2=A0=C2=A0=C2= =A0=C2=A0=C2=A0=C2=A0=C2=A0dev_wireless_status_attrs, +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0.is_visible =3D=C2=A0=C2=A0=C2= =A0dev_wireless_status_attr_is_visible, +}; + =C2=A0const struct attribute_group *usb_device_groups[] =3D { =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0&dev_attr_grp, =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0&dev_string_attr_grp, +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0&dev_wireless_status_attr_grp, =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0NULL =C2=A0}; =C2=A0 +int usb_update_wireless_status_attr(struct usb_device *udev) +{ +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0struct device *dev =3D &udev->de= v; +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0int ret; + +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0ret =3D sysfs_update_group(&dev-= >kobj, &dev_wireless_status_attr_grp); +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0if (ret < 0) +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0= =C2=A0=C2=A0=C2=A0return ret; + +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0sysfs_notify(&dev->kobj, NULL, "= wireless_status"); +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0kobject_uevent(&dev->kobj, KOBJ_= CHANGE); + +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0return 0; +} + =C2=A0/* Binary descriptors */ =C2=A0 =C2=A0static ssize_t diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h index 0eac7d4285d1..33d42d1b7d99 100644 --- a/drivers/usb/core/usb.h +++ b/drivers/usb/core/usb.h @@ -13,6 +13,7 @@ struct usb_dev_state; =C2=A0 =C2=A0extern int usb_create_sysfs_dev_files(struct usb_device *dev); =C2=A0extern void usb_remove_sysfs_dev_files(struct usb_device *dev); +extern int usb_update_wireless_status_attr(struct usb_device *dev); =C2=A0extern void usb_create_sysfs_intf_files(struct usb_interface *intf); =C2=A0extern void usb_remove_sysfs_intf_files(struct usb_interface *intf); =C2=A0extern int usb_create_ep_devs(struct device *parent, diff --git a/include/linux/usb.h b/include/linux/usb.h index d2d2f41052c0..0c527cbd7165 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -545,6 +545,12 @@ struct usb3_lpm_parameters { =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0int timeout; =C2=A0}; =C2=A0 +enum usb_wireless_status { +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0USB_WIRELESS_STATUS_NA =3D 0, +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0USB_WIRELESS_STATUS_DISCONNECTED, +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0USB_WIRELESS_STATUS_CONNECTED, +}; + =C2=A0/** =C2=A0 * struct usb_device - kernel's representation of a USB device =C2=A0 * @devnum: device number; address on a USB bus @@ -620,6 +626,8 @@ struct usb3_lpm_parameters { =C2=A0 *=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0parent->hub_delay + wHubDelay + tTPTr= ansmissionDelay (40ns) =C2=A0 *=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0Will be used as wValue for SetIsochDe= lay requests. =C2=A0 * @use_generic_driver: ask driver core to reprobe using the generic = driver. + * @wireless_status: if the USB device uses a receiver/emitter combo, whet= her + *=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0the emitter is connected. =C2=A0 * =C2=A0 * Notes: =C2=A0 * Usbcore drivers should not set usbdev->state directly.=C2=A0 Inste= ad use @@ -708,6 +716,7 @@ struct usb_device { =C2=A0 =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0u16 hub_delay; =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0unsigned use_generic_driver= :1; +=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0enum usb_wireless_status wireles= s_status; =C2=A0}; =C2=A0#define=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0to_usb_device(= d) container_of(d, struct usb_device, dev) =C2=A0 --=20 2.39.0