diff --git a/Documentation/ABI/testing/sysfs-bus-usb b/Documentation/ABI/testing/sysfs-bus-usb index 545c2dd97ed0..cb172db41b34 100644 --- a/Documentation/ABI/testing/sysfs-bus-usb +++ b/Documentation/ABI/testing/sysfs-bus-usb @@ -166,6 +166,23 @@ 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: February 2023 +Contact: Bastien Nocera +Description: + Some USB devices use a USB receiver dongle to communicate + wirelessly with their device using proprietary protocols. This + attribute allows user-space to know whether the device is + connected to its receiver dongle, and, for example, consider + the device to be absent when choosing whether to show the + device's battery, show a headset in a list of outputs, or show + an on-screen keyboard if the only wireless keyboard is + turned off. + This attribute is not to be used to replace protocol specific + statuses available in WWAN, WLAN/Wi-Fi, Bluetooth, etc. + If the device does not use a receiver dongle with a wireless + device, then this attribute will not exist. + What: /sys/bus/usb/devices/...//port Date: August 2012 Contact: Lan Tianyu diff --git a/drivers/usb/core/sysfs.c b/drivers/usb/core/sysfs.c index b63f78e48c74..323dc02becbe 100644 --- a/drivers/usb/core/sysfs.c +++ b/drivers/usb/core/sysfs.c @@ -1227,9 +1227,59 @@ static const struct attribute_group intf_assoc_attr_grp = { .is_visible = intf_assoc_attrs_are_visible, }; +static ssize_t wireless_status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_interface *intf; + + intf = to_usb_interface(dev); + if (intf->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 *intf_wireless_status_attrs[] = { + &dev_attr_wireless_status.attr, + NULL +}; + +static umode_t intf_wireless_status_attr_is_visible(struct kobject *kobj, + struct attribute *a, int n) +{ + struct device *dev = kobj_to_dev(kobj); + struct usb_interface *intf = to_usb_interface(dev); + + if (a != &dev_attr_wireless_status.attr || + intf->wireless_status != USB_WIRELESS_STATUS_NA) + return a->mode; + return 0; +} + +static const struct attribute_group intf_wireless_status_attr_grp = { + .attrs = intf_wireless_status_attrs, + .is_visible = intf_wireless_status_attr_is_visible, +}; + +int usb_update_wireless_status_attr(struct usb_interface *intf) +{ + struct device *dev = &intf->dev; + int ret; + + ret = sysfs_update_group(&dev->kobj, &intf_wireless_status_attr_grp); + if (ret < 0) + return ret; + + sysfs_notify(&dev->kobj, NULL, "wireless_status"); + kobject_uevent(&dev->kobj, KOBJ_CHANGE); + + return 0; +} + const struct attribute_group *usb_interface_groups[] = { &intf_attr_grp, &intf_assoc_attr_grp, + &intf_wireless_status_attr_grp, NULL }; diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h index 0eac7d4285d1..3f14e15f07f6 100644 --- a/drivers/usb/core/usb.h +++ b/drivers/usb/core/usb.h @@ -15,6 +15,7 @@ extern int usb_create_sysfs_dev_files(struct usb_device *dev); extern void usb_remove_sysfs_dev_files(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_update_wireless_status_attr(struct usb_interface *intf); extern int usb_create_ep_devs(struct device *parent, struct usb_host_endpoint *endpoint, struct usb_device *udev); diff --git a/include/linux/usb.h b/include/linux/usb.h index 9642ee02d713..eee54cc961b4 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h @@ -170,6 +170,12 @@ usb_find_last_int_out_endpoint(struct usb_host_interface *alt, return usb_find_common_endpoints_reverse(alt, NULL, NULL, NULL, int_out); } +enum usb_wireless_status { + USB_WIRELESS_STATUS_NA = 0, + USB_WIRELESS_STATUS_DISCONNECTED, + USB_WIRELESS_STATUS_CONNECTED, +}; + /** * struct usb_interface - what usb device drivers talk to * @altsetting: array of interface structures, one for each alternate @@ -203,6 +209,8 @@ usb_find_last_int_out_endpoint(struct usb_host_interface *alt, * @reset_ws: Used for scheduling resets from atomic context. * @resetting_device: USB core reset the device, so use alt setting 0 as * current; needs bandwidth alloc after reset. + * @wireless_status: if the USB device uses a receiver/emitter combo, whether + * the emitter is connected. * * USB device drivers attach to interfaces on a physical device. Each * interface encapsulates a single high level function, such as feeding @@ -253,6 +261,7 @@ struct usb_interface { unsigned needs_binding:1; /* needs delayed unbind/rebind */ unsigned resetting_device:1; /* true: bandwidth alloc after reset */ unsigned authorized:1; /* used for interface authorization */ + enum usb_wireless_status wireless_status; struct device dev; /* interface specific device info */ struct device *usb_dev;