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 <hadess@xxxxxxxxxx> --- Documentation/ABI/testing/sysfs-bus-usb | 11 ++++++ drivers/usb/core/sysfs.c | 50 +++++++++++++++++++++++++ drivers/usb/core/usb.h | 1 + include/linux/usb.h | 9 +++++ 4 files changed, 71 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-bus-usb b/Documentation/ABI/testing/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: The file will be present for all speeds of USB devices, and will always read "no" for USB 1.1 and USB 2.0 devices. +What: /sys/bus/usb/devices/.../wireless_status +Date: December 2022 +Contact: Bastien Nocera <hadess@xxxxxxxxxx> +Description: + Some USB devices use a small USB receiver coupled with a larger + wireless device, usually communicating using proprietary + wireless protocols. This attribute will read either "connected" + or "disconnected" depending on whether the emitter is turned on, + in range and connected. If the device does not use a receiver/ + emitter combo, then this attribute will not exist. + What: /sys/bus/usb/devices/.../<hub_interface>/port<X> Date: August 2012 Contact: Lan Tianyu <tianyu.lan@xxxxxxxxx> 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_grp = { .is_visible = dev_string_attrs_are_visible, }; +static ssize_t wireless_status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_device *udev; + + udev = to_usb_device(dev); + if (udev->wireless_status == USB_WIRELESS_STATUS_DISCONNECTED) + return sysfs_emit(buf, "%s\n", "disconnected"); + return sysfs_emit(buf, "%s\n", "connected"); +} +static DEVICE_ATTR_RO(wireless_status); + +static struct attribute *dev_wireless_status_attrs[] = { + &dev_attr_wireless_status.attr, + NULL +}; + +static umode_t dev_wireless_status_attr_is_visible(struct kobject *kobj, + struct attribute *a, int n) +{ + struct device *dev = kobj_to_dev(kobj); + struct usb_device *udev = to_usb_device(dev); + + if (a != &dev_attr_wireless_status.attr || + udev->wireless_status != USB_WIRELESS_STATUS_NA) + return a->mode; + return 0; +} + +static const struct attribute_group dev_wireless_status_attr_grp = { + .attrs = dev_wireless_status_attrs, + .is_visible = dev_wireless_status_attr_is_visible, +}; + const struct attribute_group *usb_device_groups[] = { &dev_attr_grp, &dev_string_attr_grp, + &dev_wireless_status_attr_grp, NULL }; +int usb_update_wireless_status_attr(struct usb_device *udev) +{ + struct device *dev = &udev->dev; + int ret; + + ret = sysfs_update_group(&dev->kobj, &dev_wireless_status_attr_grp); + if (ret < 0) + return ret; + + sysfs_notify(&dev->kobj, NULL, "wireless_status"); + kobject_uevent(&dev->kobj, KOBJ_CHANGE); + + return 0; +} + /* Binary descriptors */ static 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; extern int usb_create_sysfs_dev_files(struct usb_device *dev); extern void usb_remove_sysfs_dev_files(struct usb_device *dev); +extern int usb_update_wireless_status_attr(struct usb_device *dev); extern void usb_create_sysfs_intf_files(struct usb_interface *intf); extern void usb_remove_sysfs_intf_files(struct usb_interface *intf); extern 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 { int timeout; }; +enum usb_wireless_status { + USB_WIRELESS_STATUS_NA = 0, + USB_WIRELESS_STATUS_DISCONNECTED, + USB_WIRELESS_STATUS_CONNECTED, +}; + /** * struct usb_device - kernel's representation of a USB device * @devnum: device number; address on a USB bus @@ -620,6 +626,8 @@ struct usb3_lpm_parameters { * parent->hub_delay + wHubDelay + tTPTransmissionDelay (40ns) * Will be used as wValue for SetIsochDelay requests. * @use_generic_driver: ask driver core to reprobe using the generic driver. + * @wireless_status: if the USB device uses a receiver/emitter combo, whether + * the emitter is connected. * * Notes: * Usbcore drivers should not set usbdev->state directly. Instead use @@ -708,6 +716,7 @@ struct usb_device { u16 hub_delay; unsigned use_generic_driver:1; + enum usb_wireless_status wireless_status; }; #define to_usb_device(d) container_of(d, struct usb_device, dev) -- 2.39.0