• Mathias Nyman's avatar
    xhci: fix usb2 resume timing and races. · f69115fd
    Mathias Nyman authored
    According to USB 2 specs ports need to signal resume for at least 20ms,
    in practice even longer, before moving to U0 state.
    Both host and devices can initiate resume.
    
    On device initiated resume, a port status interrupt with the port in resume
    state in issued. The interrupt handler tags a resume_done[port]
    timestamp with current time + USB_RESUME_TIMEOUT, and kick roothub timer.
    Root hub timer requests for port status, finds the port in resume state,
    checks if resume_done[port] timestamp passed, and set port to U0 state.
    
    On host initiated resume, current code sets the port to resume state,
    sleep 20ms, and finally sets the port to U0 state. This should also
    be changed to work in a similar way as the device initiated resume, with
    timestamp tagging, but that is not yet tested and will be a separate
    fix later.
    
    There are a few issues with this approach
    
    1. A host initiated resume will also generate a resume event. The event
       handler will find the port in resume state, believe it's a device
       initiated resume, and act accordingly.
    
    2. A port status request might cut the resume signalling short if a
       get_port_status request is handled during the host resume signalling.
       The port will be found in resume state. The timestamp is not set leading
       to time_after_eq(jiffies, timestamp) returning true, as timestamp = 0.
       get_port_status will proceed with moving the port to U0.
    
    3. If an error, or anything else happens to the port during device
       initiated resume signalling it will leave all the device resume
       parameters hanging uncleared, preventing further suspend, returning
       -EBUSY, and cause the pm thread to busyloop trying to enter suspend.
    
    Fix this by using the existing resuming_ports bitfield to indicate that
    resume signalling timing is taken care of.
    Check if the resume_done[port] is set before using it for timestamp
    comparison, and also clear out any resume signalling related variables
    if port is not in U0 or Resume state
    
    This issue was discovered when a PM thread busylooped, trying to runtime
    suspend the xhci USB 2 roothub on a Dell XPS
    
    Cc: stable <stable@vger.kernel.org>
    Reported-by: default avatarDaniel J Blueman <daniel@quora.org>
    Tested-by: default avatarDaniel J Blueman <daniel@quora.org>
    Signed-off-by: default avatarMathias Nyman <mathias.nyman@linux.intel.com>
    Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
    f69115fd
xhci-hub.c 41.8 KB