Module Name:    src
Committed By:   riastradh
Date:           Sun May 23 11:49:45 UTC 2021

Modified Files:
        src/sys/dev/usb: xhci.c xhcireg.h xhcivar.h

Log Message:
xhci(4): Draft suspend/resume.

Work almost entirely done and tested by maya@ based on xhci 1.2 spec;
tidied up and tweaked by me.

Not sure about issuing Stop Endpoint commands or ensuring the Command
Ring is in the Stopped or Idle state, but this seems to work as is,
so it's already an improvement over what we had before which was no
xhci suspend/resume at all.

In particular, it's not clear to us:

- if we don't have any pending USB activity whether we need to issue
  the Stop Endpoints or quiesce the command ring; but

- if we do have any pending USB activity whether issuing Stop
  Endpoint is enough or whether we also need to do anything to
  synchronize with other software logic to quiesce it too.


To generate a diff of this commit:
cvs rdiff -u -r1.138 -r1.139 src/sys/dev/usb/xhci.c
cvs rdiff -u -r1.18 -r1.19 src/sys/dev/usb/xhcireg.h
cvs rdiff -u -r1.17 -r1.18 src/sys/dev/usb/xhcivar.h

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/sys/dev/usb/xhci.c
diff -u src/sys/dev/usb/xhci.c:1.138 src/sys/dev/usb/xhci.c:1.139
--- src/sys/dev/usb/xhci.c:1.138	Tue Jan  5 18:00:21 2021
+++ src/sys/dev/usb/xhci.c	Sun May 23 11:49:45 2021
@@ -1,4 +1,4 @@
-/*	$NetBSD: xhci.c,v 1.138 2021/01/05 18:00:21 skrll Exp $	*/
+/*	$NetBSD: xhci.c,v 1.139 2021/05/23 11:49:45 riastradh Exp $	*/
 
 /*
  * Copyright (c) 2013 Jonathan A. Kollasch
@@ -34,7 +34,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: xhci.c,v 1.138 2021/01/05 18:00:21 skrll Exp $");
+__KERNEL_RCSID(0, "$NetBSD: xhci.c,v 1.139 2021/05/23 11:49:45 riastradh Exp $");
 
 #ifdef _KERNEL_OPT
 #include "opt_usb.h"
@@ -388,7 +388,6 @@ xhci_rt_write_4(const struct xhci_softc 
 	bus_space_write_4(sc->sc_iot, sc->sc_rbh, offset, value);
 }
 
-#if 0 /* unused */
 static inline uint64_t
 xhci_rt_read_8(const struct xhci_softc * const sc, bus_size_t offset)
 {
@@ -408,7 +407,6 @@ xhci_rt_read_8(const struct xhci_softc *
 
 	return value;
 }
-#endif /* unused */
 
 static inline void
 xhci_rt_write_8(const struct xhci_softc * const sc, bus_size_t offset,
@@ -698,15 +696,357 @@ xhci_activate(device_t self, enum devact
 }
 
 bool
-xhci_suspend(device_t dv, const pmf_qual_t *qual)
+xhci_suspend(device_t self, const pmf_qual_t *qual)
 {
-	return false;
+	struct xhci_softc * const sc = device_private(self);
+	size_t i, j, bn;
+	int port;
+	uint32_t v;
+
+	XHCIHIST_FUNC(); XHCIHIST_CALLED();
+
+	/*
+	 * First, suspend all the ports:
+	 *
+	 * xHCI Requirements Specification 1.2, May 2019, Sec. 4.15:
+	 * Suspend-Resume, pp. 276-283
+	 * https://www.intel.com/content/dam/www/public/us/en/documents/technical-specifications/extensible-host-controler-interface-usb-xhci.pdf#page=276
+	 */
+	for (bn = 0; bn < 2; bn++) {
+		for (i = 1; i <= sc->sc_rhportcount[bn]; i++) {
+			/* 4.15.1: Port Suspend.  */
+			port = XHCI_PORTSC(xhci_rhport2ctlrport(sc, bn, i));
+
+			/*
+			 * `System software places individual ports
+			 *  into suspend mode by writing a ``3'' into
+			 *  the appropriate PORTSC register Port Link
+			 *  State (PLS) field (refer to Section 5.4.8).
+			 *  Software should only set the PLS field to
+			 *  ``3'' when the port is in the Enabled
+			 *  state.'
+			 *
+			 * `Software should not attempt to suspend a
+			 *  port unless the port reports that it is in
+			 *  the enabled (PED = ``1''; PLS < ``3'')
+			 *  state (refer to Section 5.4.8 for more
+			 *  information about PED and PLS).'
+			 */
+			v = xhci_op_read_4(sc, port);
+			if (((v & XHCI_PS_PED) == 0) ||
+			    XHCI_PS_PLS_GET(v) >= XHCI_PS_PLS_U3)
+				continue;
+			v &= ~(XHCI_PS_PLS_MASK | XHCI_PS_CLEAR);
+			v |= XHCI_PS_LWS | XHCI_PS_PLS_SET(XHCI_PS_PLS_SETU3);
+			xhci_op_write_4(sc, port, v);
+
+			/*
+			 * `When the PLS field is written with U3
+			 *  (``3''), the status of the PLS bit will not
+			 *  change to the target U state U3 until the
+			 *  suspend signaling has completed to the
+			 *  attached device (which may be as long as
+			 *  10ms.).'
+			 *
+			 * `Software is required to wait for U3
+			 *  transitions to complete before it puts the
+			 *  xHC into a low power state, and before
+			 *  resuming the port.'
+			 *
+			 * XXX Take advantage of the technique to
+			 * reduce polling on host controllers that
+			 * support the U3C capability.
+			 */
+			for (j = 0; j < XHCI_WAIT_PLS_U3; j++) {
+				v = xhci_op_read_4(sc, port);
+				if (XHCI_PS_PLS_GET(v) == XHCI_PS_PLS_U3)
+					break;
+				usb_delay_ms(&sc->sc_bus, 1);
+			}
+			if (j == XHCI_WAIT_PLS_U3) {
+				device_printf(self,
+				    "suspend timeout on bus %zu port %zu\n",
+				    bn, i);
+				return false;
+			}
+		}
+	}
+
+	/*
+	 * xHCI Requirements Specification 1.2, May 2019, Sec. 4.23.2:
+	 * xHCI Power Management, p. 342
+	 * https://www.intel.com/content/dam/www/public/us/en/documents/technical-specifications/extensible-host-controler-interface-usb-xhci.pdf#page=342
+	 */
+
+	/*
+	 * `1. Stop all USB activity by issuing Stop Endpoint Commands
+	 *     for Busy endpoints in the Running state.  If the Force
+	 *     Save Context Capability (FSC = ``0'') is not supported,
+	 *     then Stop Endpoint Commands shall be issued for all Idle
+	 *     endpoints in the Running state as well.  The Stop
+	 *     Endpoint Command causes the xHC to update the respective
+	 *     Endpoint or Stream Contexts in system memory, e.g. the
+	 *     TR Dequeue Pointer, DCS, etc. fields.  Refer to
+	 *     Implementation Note "0".'
+	 *
+	 * XXX Not entirely sure if this is necessary for us; also it
+	 * probably has to happen before suspending the ports.
+	 */
+
+	/*
+	 * `2. Ensure that the Command Ring is in the Stopped state
+	 *     (CRR = ``0'') or Idle (i.e. the Command Transfer Ring is
+	 *     empty), and all Command Completion Events associated
+	 *     with them have been received.'
+	 *
+	 * XXX
+	 */
+
+	/* `3. Stop the controller by setting Run/Stop (R/S) = ``0''.'  */
+	xhci_op_write_4(sc, XHCI_USBCMD,
+	    xhci_op_read_4(sc, XHCI_USBCMD) & ~XHCI_CMD_RS);
+
+	/*
+	 * `4. Read the Operational Runtime, and VTIO registers in the
+	 *     following order: USBCMD, DNCTRL, DCBAAP, CONFIG, ERSTSZ,
+	 *     ERSTBA, ERDP, IMAN, IMOD, and VTIO and save their
+	 *     state.'
+	 *
+	 * (We don't use VTIO here (XXX for now?).)
+	 */
+	sc->sc_regs.usbcmd = xhci_op_read_4(sc, XHCI_USBCMD);
+	sc->sc_regs.dnctrl = xhci_op_read_4(sc, XHCI_DNCTRL);
+	sc->sc_regs.dcbaap = xhci_op_read_8(sc, XHCI_DCBAAP);
+	sc->sc_regs.config = xhci_op_read_4(sc, XHCI_CONFIG);
+	sc->sc_regs.erstsz0 = xhci_rt_read_4(sc, XHCI_ERSTSZ(0));
+	sc->sc_regs.erstba0 = xhci_rt_read_8(sc, XHCI_ERSTBA(0));
+	sc->sc_regs.erdp0 = xhci_rt_read_8(sc, XHCI_ERDP(0));
+	sc->sc_regs.iman0 = xhci_rt_read_4(sc, XHCI_IMAN(0));
+	sc->sc_regs.imod0 = xhci_rt_read_4(sc, XHCI_IMOD(0));
+
+	/*
+	 * `5. Set the Controller Save State (CSS) flag in the USBCMD
+	 *     register (5.4.1)...'
+	 */
+	xhci_op_write_4(sc, XHCI_USBCMD,
+	    xhci_op_read_4(sc, XHCI_USBCMD) | XHCI_CMD_CSS);
+
+	/*
+	 *    `...and wait for the Save State Status (SSS) flag in the
+	 *     USBSTS register (5.4.2) to transition to ``0''.'
+	 */
+	for (i = 0; i < XHCI_WAIT_SSS; i++) {
+		if ((xhci_op_read_4(sc, XHCI_USBSTS) & XHCI_STS_SSS) == 0)
+			break;
+		usb_delay_ms(&sc->sc_bus, 1);
+	}
+	if (i >= XHCI_WAIT_SSS) {
+		device_printf(self, "suspend timeout, USBSTS.SSS\n");
+		/*
+		 * Just optimistically go on and check SRE anyway --
+		 * what's the worst that could happen?
+		 */
+	}
+
+	/*
+	 * `Note: After a Save or Restore operation completes, the
+	 *  Save/Restore Error (SRE) flag in the USBSTS register should
+	 *  be checked to ensure that the operation completed
+	 *  successfully.'
+	 */
+	if (xhci_op_read_4(sc, XHCI_USBSTS) & XHCI_STS_SRE) {
+		device_printf(self, "suspend error, USBSTS.SRE\n");
+		return false;
+	}
+
+	return true;
 }
 
 bool
-xhci_resume(device_t dv, const pmf_qual_t *qual)
+xhci_resume(device_t self, const pmf_qual_t *qual)
 {
-	return false;
+	struct xhci_softc * const sc = device_private(self);
+	size_t i, j, bn, dci;
+	int port;
+	uint32_t v;
+
+	XHCIHIST_FUNC(); XHCIHIST_CALLED();
+
+	/*
+	 * xHCI Requirements Specification 1.2, May 2019, Sec. 4.23.2:
+	 * xHCI Power Management, p. 343
+	 * https://www.intel.com/content/dam/www/public/us/en/documents/technical-specifications/extensible-host-controler-interface-usb-xhci.pdf#page=343
+	 */
+
+	/*
+	 * `4. Restore the Operational Runtime, and VTIO registers with
+	 *     their previously saved state in the following order:
+	 *     DNCTRL, DCBAAP, CONFIG, ERSTSZ, ERSTBA, ERDP, IMAN,
+	 *     IMOD, and VTIO.'
+	 *
+	 * (We don't use VTIO here (for now?).)
+	 */
+	xhci_op_write_4(sc, XHCI_USBCMD, sc->sc_regs.usbcmd);
+	xhci_op_write_4(sc, XHCI_DNCTRL, sc->sc_regs.dnctrl);
+	xhci_op_write_8(sc, XHCI_DCBAAP, sc->sc_regs.dcbaap);
+	xhci_op_write_4(sc, XHCI_CONFIG, sc->sc_regs.config);
+	xhci_rt_write_4(sc, XHCI_ERSTSZ(0), sc->sc_regs.erstsz0);
+	xhci_rt_write_8(sc, XHCI_ERSTBA(0), sc->sc_regs.erstba0);
+	xhci_rt_write_8(sc, XHCI_ERDP(0), sc->sc_regs.erdp0);
+	xhci_rt_write_4(sc, XHCI_IMAN(0), sc->sc_regs.iman0);
+	xhci_rt_write_4(sc, XHCI_IMOD(0), sc->sc_regs.imod0);
+
+	memset(&sc->sc_regs, 0, sizeof(sc->sc_regs)); /* paranoia */
+
+	/*
+	 * `5. Set the Controller Restore State (CRS) flag in the
+	 *     USBCMD register (5.4.1) to ``1''...'
+	 */
+	xhci_op_write_4(sc, XHCI_USBCMD,
+	    xhci_op_read_4(sc, XHCI_USBCMD) | XHCI_CMD_CRS);
+
+	/*
+	 *    `...and wait for the Restore State Status (RSS) in the
+	 *     USBSTS register (5.4.2) to transition to ``0''.'
+	 */
+	for (i = 0; i < XHCI_WAIT_RSS; i++) {
+		if ((xhci_op_read_4(sc, XHCI_USBSTS) & XHCI_STS_RSS) == 0)
+			break;
+		usb_delay_ms(&sc->sc_bus, 1);
+	}
+	if (i >= XHCI_WAIT_RSS) {
+		device_printf(self, "suspend timeout, USBSTS.RSS\n");
+		return false;
+	}
+
+	/*
+	 * `6. Reinitialize the Command Ring, i.e. so its Cycle bits
+	 *     are consistent with the RCS values to be written to the
+	 *     CRCR.'
+	 *
+	 * XXX Hope just zeroing it is good enough!
+	 */
+	xhci_host_dequeue(sc->sc_cr);
+
+	/*
+	 * `7. Write the CRCR with the address and RCS value of the
+	 *     reinitialized Command Ring.  Note that this write will
+	 *     cause the Command Ring to restart at the address
+	 *     specified by the CRCR.'
+	 */
+	xhci_op_write_8(sc, XHCI_CRCR, xhci_ring_trbp(sc->sc_cr, 0) |
+	    sc->sc_cr->xr_cs);
+
+	/*
+	 * `8. Enable the controller by setting Run/Stop (R/S) =
+	 *     ``1''.'
+	 */
+	xhci_op_write_4(sc, XHCI_USBCMD,
+	    xhci_op_read_4(sc, XHCI_USBCMD) | XHCI_CMD_RS);
+
+	/*
+	 * `9. Software shall walk the USB topology and initialize each
+	 *     of the xHC PORTSC, PORTPMSC, and PORTLI registers, and
+	 *     external hub ports attached to USB devices.'
+	 *
+	 * This follows the procedure in 4.15 `Suspend-Resume', 4.15.2
+	 * `Port Resume', 4.15.2.1 `Host Initiated'.
+	 *
+	 * XXX We should maybe batch up initiating the state
+	 * transitions, and then wait for them to complete all at once.
+	 */
+	for (bn = 0; bn < 2; bn++) {
+		for (i = 1; i <= sc->sc_rhportcount[bn]; i++) {
+			port = XHCI_PORTSC(xhci_rhport2ctlrport(sc, bn, i));
+
+			/* `When a port is in the U3 state: ...' */
+			v = xhci_op_read_4(sc, port);
+			if (XHCI_PS_PLS_GET(v) != XHCI_PS_PLS_U3)
+				continue;
+
+			/*
+			 * `For a USB2 protocol port, software shall
+			 *  write a ``15'' (Resume) to the PLS field to
+			 *  initiate resume signaling.  The port shall
+			 *  transition to the Resume substate and the
+			 *  xHC shall transmit the resume signaling
+			 *  within 1ms (T_URSM).  Software shall ensure
+			 *  that resume is signaled for at least 20ms
+			 *  (T_DRSMDN).  Software shall start timing
+			 *  T_DRSMDN from the write of ``15'' (Resume)
+			 *  to PLS.'
+			 */
+			if (bn == 1) {
+				KASSERT(sc->sc_bus2.ub_revision == USBREV_2_0);
+				v &= ~(XHCI_PS_PLS_MASK | XHCI_PS_CLEAR);
+				v |= XHCI_PS_LWS;
+				v |= XHCI_PS_PLS_SET(XHCI_PS_PLS_SETRESUME);
+				xhci_op_write_4(sc, port, v);
+				usb_delay_ms(&sc->sc_bus, 20);
+			} else {
+				KASSERT(sc->sc_bus.ub_revision > USBREV_2_0);
+			}
+
+			/*
+			 * `For a USB3 protocol port [and a USB2
+			 *  protocol port after transitioning to
+			 *  Resume], software shall write a ``0'' (U0)
+			 *  to the PLS field...'
+			 */
+			v = xhci_op_read_4(sc, port);
+			v &= ~(XHCI_PS_PLS_MASK | XHCI_PS_CLEAR);
+			v |= XHCI_PS_LWS | XHCI_PS_PLS_SET(XHCI_PS_PLS_SETU0);
+			xhci_op_write_4(sc, port, v);
+
+			for (j = 0; j < XHCI_WAIT_PLS_U0; j++) {
+				v = xhci_op_read_4(sc, port);
+				if (XHCI_PS_PLS_GET(v) == XHCI_PS_PLS_U0)
+					break;
+				usb_delay_ms(&sc->sc_bus, 1);
+			}
+			if (j == XHCI_WAIT_PLS_U0) {
+				device_printf(self,
+				    "resume timeout on bus %zu port %zu\n",
+				    bn, i);
+				return false;
+			}
+		}
+	}
+
+	/*
+	 * `10. Restart each of the previously Running endpoints by
+	 *      ringing their doorbells.'
+	 */
+	for (i = 0; i < sc->sc_maxslots; i++) {
+		struct xhci_slot *xs = &sc->sc_slots[i];
+
+		/* Skip if the slot is not in use.  */
+		if (xs->xs_idx == 0)
+			continue;
+
+		for (dci = XHCI_DCI_SLOT; dci <= XHCI_MAX_DCI; dci++) {
+			/* Skip if the endpoint is not Running.  */
+			if (xhci_get_epstate(sc, xs, dci) !=
+			    XHCI_EPSTATE_RUNNING)
+				continue;
+
+			/* Ring the doorbell.  */
+			xhci_db_write_4(sc, XHCI_DOORBELL(xs->xs_idx), dci);
+		}
+	}
+
+	/*
+	 * `Note: After a Save or Restore operation completes, the
+	 *  Save/Restore Error (SRE) flag in the USBSTS register should
+	 *  be checked to ensure that the operation completed
+	 *  successfully.'
+	 */
+	if (xhci_op_read_4(sc, XHCI_USBSTS) & XHCI_STS_SRE) {
+		device_printf(self, "resume error, USBSTS.SRE\n");
+		return false;
+	}
+
+	return true;
 }
 
 bool
@@ -772,7 +1112,6 @@ xhci_hc_reset(struct xhci_softc * const 
 	return 0;
 }
 
-
 /* 7.2 xHCI Support Protocol Capability */
 static void
 xhci_id_protocols(struct xhci_softc *sc, bus_size_t ecp)

Index: src/sys/dev/usb/xhcireg.h
diff -u src/sys/dev/usb/xhcireg.h:1.18 src/sys/dev/usb/xhcireg.h:1.19
--- src/sys/dev/usb/xhcireg.h:1.18	Sat Jun  6 08:56:30 2020
+++ src/sys/dev/usb/xhcireg.h	Sun May 23 11:49:45 2021
@@ -1,4 +1,4 @@
-/* $NetBSD: xhcireg.h,v 1.18 2020/06/06 08:56:30 skrll Exp $ */
+/* $NetBSD: xhcireg.h,v 1.19 2021/05/23 11:49:45 riastradh Exp $ */
 
 /*-
  * Copyright (c) 2010 Hans Petter Selasky. All rights reserved.
@@ -132,6 +132,10 @@
 
 #define	XHCI_WAIT_CNR		100		/* in 1ms */
 #define	XHCI_WAIT_HCRST		100		/* in 1ms */
+#define	XHCI_WAIT_RSS		100		/* in 1ms */
+#define	XHCI_WAIT_SSS		100		/* in 1ms */
+#define	XHCI_WAIT_PLS_U0	100		/* in 1ms */
+#define	XHCI_WAIT_PLS_U3	10		/* in 1ms */
 
 #define	XHCI_USBSTS		0x04	/* XHCI status */
 #define	 XHCI_STS_HCH		__BIT(0)	/* RO - Host Controller Halted */

Index: src/sys/dev/usb/xhcivar.h
diff -u src/sys/dev/usb/xhcivar.h:1.17 src/sys/dev/usb/xhcivar.h:1.18
--- src/sys/dev/usb/xhcivar.h:1.17	Fri Aug 21 20:46:03 2020
+++ src/sys/dev/usb/xhcivar.h	Sun May 23 11:49:45 2021
@@ -1,4 +1,4 @@
-/*	$NetBSD: xhcivar.h,v 1.17 2020/08/21 20:46:03 jakllsch Exp $	*/
+/*	$NetBSD: xhcivar.h,v 1.18 2021/05/23 11:49:45 riastradh Exp $	*/
 
 /*
  * Copyright (c) 2013 Jonathan A. Kollasch
@@ -137,6 +137,18 @@ struct xhci_softc {
 #define XHCI_DEFERRED_START	__BIT(1)
 	uint32_t sc_hcc;		/* copy of HCCPARAMS1 */
 	uint32_t sc_hcc2;		/* copy of HCCPARAMS2 */
+
+	struct xhci_registers {
+		uint32_t	usbcmd;
+		uint32_t	dnctrl;
+		uint64_t	dcbaap;
+		uint32_t	config;
+		uint32_t	erstsz0;
+		uint64_t	erstba0;
+		uint64_t	erdp0;
+		uint32_t	iman0;
+		uint32_t	imod0;
+	} sc_regs;
 };
 
 int	xhci_init(struct xhci_softc *);

Reply via email to