Re: usb: host: xhci: Fix Compliance Mode on SN65LVPE502CP Hardware

From: Alexis R. Cortes
Date: Tue Aug 14 2012 - 12:13:58 EST


Hi Sarah,

I was wondering if you have any news regarding this patch, or if there's something else I need to change on code. I also noticed that I forgot to write '[PATCH]' at the beginning of the patch's subject, Should I resend it?

Thanks and Best Regards,
Alexis Cortes.

On 8/3/2012 2:00 PM, Alexis R. Cortes wrote:
> This patch is intended to work around a known issue on the
> SN65LVPE502CP USB3.0 re-driver that can delay the negotiation
> between a device and the host past the usual handshake timeout.
>
> If that happens on the first insertion, the host controller
> port will enter in Compliance Mode and NO port status event will
> be generated (as per xHCI Spec) making impossible to detect this
> event by software. The port will remain in compliance mode until
> a warm reset is applied to it.
>
> As a result of this, the port will seem "dead" to the user and no
> device connections or disconnections will be detected.
>
> For solving this, the patch creates a timer which polls every 2
> seconds the link state of each host controller's port (this
> by reading the PORTSC register) and recovers the port by issuing a
> Warm reset every time Compliance mode is detected.
>
> If a xHC USB3.0 port has previously entered to U0, the compliance
> mode issue will NOT occur only until system resumes from
> sleep/hibernate, therefore, the compliance mode timer is stopped
> when all xHC USB 3.0 ports have entered U0. The timer is initialized
> again after each system resume.
>
> Since the issue is being caused by a pice of hardware, the timer
> will be enabled ONLY on those systems that have the SN65LVPE502CP
> installed (this patch uses DMI strings for detecting those systems)
> therefore making this patch to act as a quirk (XHCI_COMP_MODE_QUIRK
> has been added to the xhci stack).
>
> This patch applies for these systems:
> Vendor: Hewlett-Packard. System Models: Z420, Z620 and Z820.
>
> Signed-off-by: Alexis R. Cortes <alexis.cortes@xxxxxx>
> ---
> drivers/usb/host/xhci-hub.c | 42 +++++++++++++++
> drivers/usb/host/xhci.c | 121 +++++++++++++++++++++++++++++++++++++++++++
> drivers/usb/host/xhci.h | 6 ++
> 3 files changed, 169 insertions(+), 0 deletions(-)
>
> diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c
> index 7b01094..32ca289 100644
> --- a/drivers/usb/host/xhci-hub.c
> +++ b/drivers/usb/host/xhci-hub.c
> @@ -493,11 +493,48 @@ static void xhci_hub_report_link_state(u32 *status, u32 status_reg)
> * when this bit is set.
> */
> pls |= USB_PORT_STAT_CONNECTION;
> + } else {
> + /*
> + * If CAS bit isn't set but the Port is already at
> + * Compliance Mode, fake a connection so the USB core
> + * notices the Compliance state and resets the port.
> + * This resolves an issue generated by the SN65LVPE502CP
> + * in which sometimes the port enters compliance mode
> + * caused by a delay on the host-device negotiation.
> + */
> + if (pls == USB_SS_PORT_LS_COMP_MOD)
> + pls |= USB_PORT_STAT_CONNECTION;
> }
> +
> /* update status field */
> *status |= pls;
> }
>
> +/*
> + * Function for Compliance Mode Quirk.
> + *
> + * This Function verifies if all xhc USB3 ports have entered U0, if so,
> + * the compliance mode timer is deleted. A port won't enter
> + * compliance mode if it has previously entered U0.
> + */
> +void xhci_del_comp_mod_timer(struct xhci_hcd *xhci, u32 status, u16 wIndex)
> +{
> + u32 all_ports_seen_u0 = ((1 << xhci->num_usb3_ports)-1);
> + bool port_in_u0 = ((status & PORT_PLS_MASK) == XDEV_U0);
> +
> + if (!(xhci->quirks & XHCI_COMP_MODE_QUIRK))
> + return;
> +
> + if ((xhci->port_status_u0 != all_ports_seen_u0) && port_in_u0) {
> + xhci->port_status_u0 |= 1 << wIndex;
> + if (xhci->port_status_u0 == all_ports_seen_u0) {
> + del_timer_sync(&xhci->comp_mode_recovery_timer);
> + xhci_dbg(xhci, "All USB3 ports have entered U0 already!\n");
> + xhci_dbg(xhci, "Compliance Mode Recovery Timer Deleted.\n");
> + }
> + }
> +}
> +
> int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
> u16 wIndex, char *buf, u16 wLength)
> {
> @@ -645,6 +682,11 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
> /* Update Port Link State for super speed ports*/
> if (hcd->speed == HCD_USB3) {
> xhci_hub_report_link_state(&status, temp);
> + /*
> + * Verify if all USB3 Ports Have entered U0 already.
> + * Delete Compliance Mode Timer if so.
> + */
> + xhci_del_comp_mod_timer(xhci, temp, wIndex);
> }
> if (bus_state->port_c_suspend & (1 << wIndex))
> status |= 1 << USB_PORT_FEAT_C_SUSPEND;
> diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
> index a979cd0..347a29f 100644
> --- a/drivers/usb/host/xhci.c
> +++ b/drivers/usb/host/xhci.c
> @@ -26,6 +26,7 @@
> #include <linux/module.h>
> #include <linux/moduleparam.h>
> #include <linux/slab.h>
> +#include <linux/dmi.h>
>
> #include "xhci.h"
>
> @@ -397,6 +398,95 @@ static void xhci_msix_sync_irqs(struct xhci_hcd *xhci)
>
> #endif
>
> +static void compliance_mode_recovery(unsigned long arg)
> +{
> + struct xhci_hcd *xhci;
> + struct usb_hcd *hcd;
> + u32 temp;
> + int i;
> +
> + xhci = (struct xhci_hcd *)arg;
> +
> + for (i = 0; i < xhci->num_usb3_ports; i++) {
> + temp = xhci_readl(xhci, xhci->usb3_ports[i]);
> + if ((temp & PORT_PLS_MASK) == USB_SS_PORT_LS_COMP_MOD) {
> + /*
> + * Compliance Mode Detected. Letting USB Core
> + * handle the Warm Reset
> + */
> + xhci_dbg(xhci, "Compliance Mode Detected->Port %d!\n",
> + i + 1);
> + xhci_dbg(xhci, "Attempting Recovery routine!\n");
> + hcd = xhci->shared_hcd;
> +
> + if (hcd->state == HC_STATE_SUSPENDED)
> + usb_hcd_resume_root_hub(hcd);
> +
> + usb_hcd_poll_rh_status(hcd);
> + }
> + }
> +
> + if (xhci->port_status_u0 != ((1 << xhci->num_usb3_ports)-1))
> + mod_timer(&xhci->comp_mode_recovery_timer,
> + jiffies + msecs_to_jiffies(COMP_MODE_RCVRY_MSECS));
> +}
> +
> +/*
> + * Quirk to work around issue generated by the SN65LVPE502CP USB3.0 re-driver
> + * that causes ports behind that hardware to enter compliance mode sometimes.
> + * The quirk creates a timer that polls every 2 seconds the link state of
> + * each host controller's port and recovers it by issuing a Warm reset
> + * if Compliance mode is detected, otherwise the port will become "dead" (no
> + * device connections or disconnections will be detected anymore). Becasue no
> + * status event is generated when entering compliance mode (per xhci spec),
> + * this quirk is needed on systems that have the failing hardware installed.
> + */
> +static void compliance_mode_recovery_timer_init(struct xhci_hcd *xhci)
> +{
> + xhci->port_status_u0 = 0;
> + init_timer(&xhci->comp_mode_recovery_timer);
> +
> + xhci->comp_mode_recovery_timer.data = (unsigned long) xhci;
> + xhci->comp_mode_recovery_timer.function = compliance_mode_recovery;
> + xhci->comp_mode_recovery_timer.expires = jiffies +
> + msecs_to_jiffies(COMP_MODE_RCVRY_MSECS);
> +
> + set_timer_slack(&xhci->comp_mode_recovery_timer,
> + msecs_to_jiffies(COMP_MODE_RCVRY_MSECS));
> + add_timer(&xhci->comp_mode_recovery_timer);
> + xhci_dbg(xhci, "Compliance Mode Recovery Timer Initialized.\n");
> +}
> +
> +/*
> + * This function identifies the systems that have installed the SN65LVPE502CP
> + * USB3.0 re-driver and that need the Compliance Mode Quirk.
> + * Systems:
> + * Vendor: Hewlett-Packard -> System Models: Z420, Z620 and Z820
> + */
> +static bool compliance_mode_recovery_timer_quirk_check(void)
> +{
> + const char *dmi_product_name, *dmi_sys_vendor;
> +
> + dmi_product_name = dmi_get_system_info(DMI_PRODUCT_NAME);
> + dmi_sys_vendor = dmi_get_system_info(DMI_SYS_VENDOR);
> +
> + if (!(strstr(dmi_sys_vendor, "Hewlett-Packard")))
> + return false;
> +
> + if (strstr(dmi_product_name, "Z420") ||
> + strstr(dmi_product_name, "Z620") ||
> + strstr(dmi_product_name, "Z820"))
> + return true;
> +
> + return false;
> +}
> +
> +static int xhci_all_ports_seen_u0(struct xhci_hcd *xhci)
> +{
> + return (xhci->port_status_u0 == ((1 << xhci->num_usb3_ports)-1));
> +}
> +
> +
> /*
> * Initialize memory for HCD and xHC (one-time init).
> *
> @@ -420,6 +510,12 @@ int xhci_init(struct usb_hcd *hcd)
> retval = xhci_mem_init(xhci, GFP_KERNEL);
> xhci_dbg(xhci, "Finished xhci_init\n");
>
> + /* Initializing Compliance Mode Recovery Data If Needed */
> + if (compliance_mode_recovery_timer_quirk_check()) {
> + xhci->quirks |= XHCI_COMP_MODE_QUIRK;
> + compliance_mode_recovery_timer_init(xhci);
> + }
> +
> return retval;
> }
>
> @@ -628,6 +724,11 @@ void xhci_stop(struct usb_hcd *hcd)
> del_timer_sync(&xhci->event_ring_timer);
> #endif
>
> + /* Deleting Compliance Mode Recovery Timer */
> + if ((xhci->quirks & XHCI_COMP_MODE_QUIRK) &&
> + (!(xhci_all_ports_seen_u0(xhci))))
> + del_timer_sync(&xhci->comp_mode_recovery_timer);
> +
> if (xhci->quirks & XHCI_AMD_PLL_FIX)
> usb_amd_dev_put();
>
> @@ -802,6 +903,16 @@ int xhci_suspend(struct xhci_hcd *xhci)
> }
> spin_unlock_irq(&xhci->lock);
>
> + /*
> + * Deleting Compliance Mode Recovery Timer because the xHCI Host
> + * is about to be suspended.
> + */
> + if ((xhci->quirks & XHCI_COMP_MODE_QUIRK) &&
> + (!(xhci_all_ports_seen_u0(xhci)))) {
> + del_timer_sync(&xhci->comp_mode_recovery_timer);
> + xhci_dbg(xhci, "Compliance Mode Recovery Timer Deleted!\n");
> + }
> +
> /* step 5: remove core well power */
> /* synchronize irq when using MSI-X */
> xhci_msix_sync_irqs(xhci);
> @@ -934,6 +1045,16 @@ int xhci_resume(struct xhci_hcd *xhci, bool hibernated)
> usb_hcd_resume_root_hub(hcd);
> usb_hcd_resume_root_hub(xhci->shared_hcd);
> }
> +
> + /*
> + * If system is subject to the Quirk, Compliance Mode Timer needs to
> + * be re-initialized Always after a system resume. Ports are subject
> + * to suffer the Compliance Mode issue again. It doesn't matter if
> + * ports have entered previously to U0 before system's suspension.
> + */
> + if (xhci->quirks & XHCI_COMP_MODE_QUIRK)
> + compliance_mode_recovery_timer_init(xhci);
> +
> return retval;
> }
> #endif /* CONFIG_PM */
> diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
> index 55c0785..f1e7874 100644
> --- a/drivers/usb/host/xhci.h
> +++ b/drivers/usb/host/xhci.h
> @@ -1494,6 +1494,7 @@ struct xhci_hcd {
> #define XHCI_TRUST_TX_LENGTH (1 << 10)
> #define XHCI_LPM_SUPPORT (1 << 11)
> #define XHCI_INTEL_HOST (1 << 12)
> +#define XHCI_COMP_MODE_QUIRK (1 << 13)
> unsigned int num_active_eps;
> unsigned int limit_active_eps;
> /* There are two roothubs to keep track of bus suspend info for */
> @@ -1510,6 +1511,11 @@ struct xhci_hcd {
> unsigned sw_lpm_support:1;
> /* support xHCI 1.0 spec USB2 hardware LPM */
> unsigned hw_lpm_support:1;
> + /* Compliance Mode Recovery Data */
> + struct timer_list comp_mode_recovery_timer;
> + u32 port_status_u0;
> +/* Compliance Mode Timer Triggered every 2 seconds */
> +#define COMP_MODE_RCVRY_MSECS 2000
> };
>
> /* convert between an HCD pointer and the corresponding EHCI_HCD */
>

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/