Re: [PATCH v3] usb: core: add sysfs entry for usb device state

From: Greg Kroah-Hartman
Date: Wed Jun 07 2023 - 15:06:13 EST


On Mon, Jun 05, 2023 at 09:55:28PM +0000, Roy Luo wrote:
> Expose usb device state to userland as the information is useful in
> detecting non-compliant setups and diagnosing enumeration failures.
> For example:
> - End-to-end signal integrity issues: the device would fail port reset
> repeatedly and thus be stuck in POWERED state.
> - Charge-only cables (missing D+/D- lines): the device would never enter
> POWERED state as the HC would not see any pullup.
>
> What's the status quo?
> We do have error logs such as "Cannot enable. Maybe the USB cable is bad?"
> to flag potential setup issues, but there's no good way to expose them to
> userspace.
>
> Why add a sysfs entry in struct usb_port instead of struct usb_device?
> The struct usb_device is not device_add() to the system until it's in
> ADDRESS state hence we would miss the first two states. The struct
> usb_port is a better place to keep the information because its life
> cycle is longer than the struct usb_device that is attached to the port.
>
> Reported-by: kernel test robot <oliver.sang@xxxxxxxxx>
> Closes: https://lore.kernel.org/oe-lkp/202306042228.e532af6e-oliver.sang@xxxxxxxxx
> Signed-off-by: Roy Luo <royluo@xxxxxxxxxx>
> ---
> This patch comes directly from RFC v2 after being reviewed by Alan Stern
> Link: https://lore.kernel.org/all/20230531010134.1092942-1-royluo@xxxxxxxxxx/
> More discussion about implementation options is in RFC v1
> Link: https://lore.kernel.org/all/20230525173818.219633-1-royluo@xxxxxxxxxx/
>
> Changes since v1:
> * Address Alan Stern's comment: remove redundant NULL initializers in
> update_port_device_state().
>
> Changes since v2:
> * Fix "BUG: sleeping function called from invalid context" caught by
> kernel test robot. Move sleeping function sysfs_get_dirent to port
> initiailzation and keep the kernfs_node for future reference.
> (Reviewed-by tag is reset as this patch involves more code changes)
> ---
> Documentation/ABI/testing/sysfs-bus-usb | 9 +++++++
> drivers/usb/core/hub.c | 15 ++++++++++++
> drivers/usb/core/hub.h | 4 ++++
> drivers/usb/core/port.c | 32 +++++++++++++++++++++----
> 4 files changed, 56 insertions(+), 4 deletions(-)
>
> diff --git a/Documentation/ABI/testing/sysfs-bus-usb b/Documentation/ABI/testing/sysfs-bus-usb
> index cb172db41b34..155770f18f9c 100644
> --- a/Documentation/ABI/testing/sysfs-bus-usb
> +++ b/Documentation/ABI/testing/sysfs-bus-usb
> @@ -292,6 +292,15 @@ Description:
> which is marked with early_stop has failed to initialize, it will ignore
> all future connections until this attribute is clear.
>
> +What: /sys/bus/usb/devices/.../<hub_interface>/port<X>/state
> +Date: May 2023

It's June :)

> +Contact: Roy Luo <royluo@xxxxxxxxxx>
> +Description:
> + Indicates current state of the USB device attached to the port. Valid
> + states are: 'not-attached', 'attached', 'powered',
> + 'reconnecting', 'unauthenticated', 'default', 'addressed',
> + 'configured', and 'suspended'.

No mentioning that you can poll on this file? You went through a lot of
work to add that feature, right? Or am I mistaking why you added the
sysfs_notify_dirent() calls? Are they really not needed? Or are they
needed?

Actually why are they needed? This is a state change, can't you emit
uevents saying the state changed and if userspace wants to re-read it,
it will? Or is that too noisy?

Or is any of this needed at all, and you just want to read the file
every once in a while from userspace to see what is going on?

Do you have a pointer to any userspace code that is using this new
interface?

> +
> What: /sys/bus/usb/devices/.../power/usb2_lpm_l1_timeout
> Date: May 2013
> Contact: Mathias Nyman <mathias.nyman@xxxxxxxxxxxxxxx>
> diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
> index 97a0f8faea6e..a739403a9e45 100644
> --- a/drivers/usb/core/hub.c
> +++ b/drivers/usb/core/hub.c
> @@ -2018,6 +2018,19 @@ bool usb_device_is_owned(struct usb_device *udev)
> return !!hub->ports[udev->portnum - 1]->port_owner;
> }
>
> +static void update_port_device_state(struct usb_device *udev)
> +{
> + struct usb_hub *hub;
> + struct usb_port *port_dev;
> +
> + if (udev->parent) {
> + hub = usb_hub_to_struct_hub(udev->parent);
> + port_dev = hub->ports[udev->portnum - 1];
> + WRITE_ONCE(port_dev->state, udev->state);
> + sysfs_notify_dirent(port_dev->state_kn);
> + }
> +}
> +
> static void recursively_mark_NOTATTACHED(struct usb_device *udev)
> {
> struct usb_hub *hub = usb_hub_to_struct_hub(udev);
> @@ -2030,6 +2043,7 @@ static void recursively_mark_NOTATTACHED(struct usb_device *udev)
> if (udev->state == USB_STATE_SUSPENDED)
> udev->active_duration -= jiffies;
> udev->state = USB_STATE_NOTATTACHED;
> + update_port_device_state(udev);
> }
>
> /**
> @@ -2086,6 +2100,7 @@ void usb_set_device_state(struct usb_device *udev,
> udev->state != USB_STATE_SUSPENDED)
> udev->active_duration += jiffies;
> udev->state = new_state;
> + update_port_device_state(udev);
> } else
> recursively_mark_NOTATTACHED(udev);
> spin_unlock_irqrestore(&device_state_lock, flags);
> diff --git a/drivers/usb/core/hub.h b/drivers/usb/core/hub.h
> index e23833562e4f..37897afd1b64 100644
> --- a/drivers/usb/core/hub.h
> +++ b/drivers/usb/core/hub.h
> @@ -84,6 +84,8 @@ struct usb_hub {
> * @peer: related usb2 and usb3 ports (share the same connector)
> * @req: default pm qos request for hubs without port power control
> * @connect_type: port's connect type
> + * @state: device state of the usb device attached to the port
> + * @state_kn: kernfs_node of the sysfs attribute that accesses @state
> * @location: opaque representation of platform connector location
> * @status_lock: synchronize port_event() vs usb_port_{suspend|resume}
> * @portnum: port index num based one
> @@ -100,6 +102,8 @@ struct usb_port {
> struct usb_port *peer;
> struct dev_pm_qos_request *req;
> enum usb_port_connect_type connect_type;
> + enum usb_device_state state;
> + struct kernfs_node *state_kn;
> usb_port_location_t location;
> struct mutex status_lock;
> u32 over_current_count;
> diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c
> index 06a8f1f84f6f..2f906e89054e 100644
> --- a/drivers/usb/core/port.c
> +++ b/drivers/usb/core/port.c
> @@ -160,6 +160,16 @@ static ssize_t connect_type_show(struct device *dev,
> }
> static DEVICE_ATTR_RO(connect_type);
>
> +static ssize_t state_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct usb_port *port_dev = to_usb_port(dev);
> + enum usb_device_state state = READ_ONCE(port_dev->state);
> +
> + return sprintf(buf, "%s\n", usb_state_string(state));

I thought checkpatch would warn you that you should be using
sysfs_emit() here, wonder why it didn't.

thanks,

greg k-h