Index | Thread | Search

From:
Mike Larkin <mlarkin@nested.page>
Subject:
Re: urtwn: power the RTL8188CU/8192CU off in urtwn_stop()
To:
Mikolaj Kucharski <mikolaj@kucharski.name>
Cc:
tech@openbsd.org
Date:
Fri, 30 Oct 2026 02:00:56 -0700

Download raw body.

Thread
On Thu, Oct 29, 2026 at 08:04:34PM +0000, Mikolaj Kucharski wrote:
> Hi.
>
> Below diff was tested together with fw-cmd fix from thread:
>
>    https://marc.info/?t=179243536000002&r=1&w=2
>
> Together those diffs make my urtwn(4) very reliable. For my hardware it
> is a day and night difference. I cannot break it. There are still many
> things to be backported or borrowed from FreeBSD, however.
>
>    urtwn0 at uhub0 port 4 configuration 1 interface 0 "Realtek 802.11n WLAN Adapter" rev 2.00/2.00 addr 3
>    urtwn0: poweroff fix v1 (2026-10-27) applied
>    urtwn0: MAC/BB RTL8188CUS, RF 6052 1T1R, address 00:13:ef:40:51:2a
>
> I have more bugs discovered and exposed by testing which I did on my
> urtwn(4) card with athn(4) access point, but that is for another time.
> I would appreciated review of both diffs.
>
> Those diffs were heavily assisted by Claude.
>

see below

>
>
> urtwn: power the RTL8188CU/8192CU off in urtwn_stop()
>
> urtwn_stop() only aborted the USB transfers and left the chip
> running, so every later init (ENETRESET, ifconfig down/up, the Tx
> watchdog) reprogrammed a live chip: no BB/RF reset, MAC Tx queues
> and page accounting kept stale state, firmware reloaded into a
> running 8051.  Under Tx load the device could wedge with "device
> timeout", and the watchdog's reinit did not recover it; only a
> replug did.
>
> Power the chip off on stop, ported from FreeBSD
> urtwn_r92c_power_off() (6e2ab416930, R92C path mostly from NetBSD),
> so each init starts from the same state as after a replug.  Bring
> back the APSD_CTRL "Initialize MAC" step in urtwn_r92c_power_on(),
> removed in r1.85 when nothing turned the MAC off; FreeBSD still has
> it.  RTL8188E/8188F/8192E are left alone.
>
> Tested on RTL8188CUS with an athn(4) hostap AP.
>
> Index: sys/dev/ic/r92creg.h
> ===================================================================
> RCS file: /cvs/src/sys/dev/ic/r92creg.h,v
> diff -u -p -r1.31 r92creg.h
> --- sys/dev/ic/r92creg.h	1 Sep 2024 03:14:48 -0000	1.31
> +++ sys/dev/ic/r92creg.h	29 Oct 2026 19:52:32 -0000
> @@ -52,6 +52,7 @@
>  #define R92C_GPIO_IO_SEL		0x042
>  #define R92C_MAC_PINMUX_CFG		0x043
>  #define R92C_GPIO_PIN_CTRL		0x044
> +#define R92C_GPIO_IOSEL			0x046
>  #define R92C_GPIO_INTM			0x048
>  #define R92C_LEDCFG0			0x04c
>  #define R92C_LEDCFG1			0x04d
> @@ -346,6 +347,12 @@
>  #define R92C_RF_CTRL_EN		0x01
>  #define R92C_RF_CTRL_RSTB	0x02
>  #define R92C_RF_CTRL_SDMRSTB	0x04
> +
> +/* Bits for R92C_LDOA15_CTRL. */
> +#define R92C_LDOA15_CTRL_EN		0x01
> +#define R92C_LDOA15_CTRL_STBY		0x02
> +#define R92C_LDOA15_CTRL_OBUF		0x04
> +#define R92C_LDOA15_CTRL_REG_VOS	0x08
>
>  /* Bits for R92C_LDOV12D_CTRL. */
>  #define R92C_LDOV12D_CTRL_LDV12_EN	0x01
> Index: sys/dev/ic/rtwnvar.h
> ===================================================================
> RCS file: /cvs/src/sys/dev/ic/rtwnvar.h,v
> diff -u -p -r1.16 rtwnvar.h
> --- sys/dev/ic/rtwnvar.h	28 Apr 2023 01:24:14 -0000	1.16
> +++ sys/dev/ic/rtwnvar.h	29 Oct 2026 19:52:32 -0000
> @@ -135,3 +135,4 @@ void		rtwn_delete_key(struct ieee80211co
>  int		rtwn_ioctl(struct ifnet *, u_long, caddr_t);
>  void		rtwn_start(struct ifnet *);
>  void		rtwn_fw_reset(struct rtwn_softc *);
> +void		rtwn_rf_write(struct rtwn_softc *, int, uint16_t, uint32_t);
> Index: sys/dev/usb/if_urtwn.c
> ===================================================================
> RCS file: /cvs/src/sys/dev/usb/if_urtwn.c,v
> diff -u -p -r1.114 if_urtwn.c
> --- sys/dev/usb/if_urtwn.c	18 Jun 2025 13:48:23 -0000	1.114
> +++ sys/dev/usb/if_urtwn.c	29 Oct 2026 19:52:32 -0000
> @@ -399,6 +399,7 @@ int		urtwn_ioctl(struct ifnet *, u_long,
>  int		urtwn_power_on(void *);
>  int		urtwn_alloc_buffers(void *);
>  int		urtwn_r92c_power_on(struct urtwn_softc *);
> +void		urtwn_r92c_power_off(struct urtwn_softc *);
>  int		urtwn_r92e_power_on(struct urtwn_softc *);
>  int		urtwn_r88e_power_on(struct urtwn_softc *);
>  int		urtwn_r88f_power_on(struct urtwn_softc *);
> @@ -452,6 +453,9 @@ urtwn_attach(struct device *parent, stru
>  	sc->sc_iface = uaa->iface;
>
>  	sc->sc_sc.chip = urtwn_lookup(uaa->vendor, uaa->product)->chip;
> +	/* POWEROFF TEST: banner, remove before commit. */
> +	printf("%s: poweroff fix v1 (2026-10-27) applied\n",
> +	    sc->sc_dev.dv_xname);
>

remove

>  	usb_init_task(&sc->sc_task, urtwn_task, sc, USB_TASK_TYPE_GENERIC);
>  	timeout_set(&sc->scan_to, urtwn_scan_to, sc);
> @@ -1822,6 +1826,21 @@ urtwn_r92c_power_on(struct urtwn_softc *
>  	urtwn_write_2(sc, R92C_SYS_ISO_CTRL,
>  	    urtwn_read_2(sc, R92C_SYS_ISO_CTRL) & ~R92C_SYS_ISO_CTRL_DIOR);
>
> +	/* Initialize MAC (urtwn_r92c_power_off() turns it off). */
> +	urtwn_write_1(sc, R92C_APSD_CTRL,
> +	    urtwn_read_1(sc, R92C_APSD_CTRL) & ~R92C_APSD_CTRL_OFF);
> +	for (ntries = 0; ntries < 1000; ntries++) {
> +		if (!(urtwn_read_1(sc, R92C_APSD_CTRL) &
> +		    R92C_APSD_CTRL_OFF_STATUS))
> +			break;
> +		DELAY(50);
> +	}
> +	if (ntries == 1000) {
> +		printf("%s: timeout waiting for MAC initialization\n",
> +		    sc->sc_dev.dv_xname);
> +		return (ETIMEDOUT);
> +	}
> +
>  	/* Enable MAC DMA/WMAC/SCHEDULE/SEC blocks. */
>  	reg = urtwn_read_2(sc, R92C_CR);
>  	reg |= R92C_CR_HCI_TXDMA_EN | R92C_CR_HCI_RXDMA_EN |
> @@ -2592,6 +2611,118 @@ urtwn_stop(void *cookie)
>  	/* Free Tx/Rx buffers. */
>  	urtwn_free_tx_list(sc);
>  	urtwn_free_rx_list(sc);
> +
> +	/*
> +	 * Power the chip off, so that the next init starts from the same
> +	 * state as after a replug.  A warm init does not reset the BB, RF,
> +	 * MAC Tx queues or 8051.
> +	 */
> +	if (!usbd_is_dying(sc->sc_udev) &&
> +	    !(sc->sc_sc.chip & (RTWN_CHIP_88E | RTWN_CHIP_88F | RTWN_CHIP_92E)))
> +		urtwn_r92c_power_off(sc);
> +}
> +
> +/*
> + * From FreeBSD urtwn_r92c_power_off(), commit 6e2ab416930 "urtwn: shutdown
> + * the device properly" (D5198; R92C path mostly from NetBSD).
> + */
> +void
> +urtwn_r92c_power_off(struct urtwn_softc *sc)
> +{
> +	uint32_t reg;
> +
> +	/* Block all Tx queues. */
> +	urtwn_write_1(sc, R92C_TXPAUSE, R92C_TXPAUSE_ALL);
> +
> +	/* Disable RF. */
> +	rtwn_rf_write(&sc->sc_sc, 0, 0, 0);
> +
> +	urtwn_write_1(sc, R92C_APSD_CTRL, R92C_APSD_CTRL_OFF);
> +
> +	/* Reset BB state machine. */
> +	urtwn_write_1(sc, R92C_SYS_FUNC_EN,
> +	    R92C_SYS_FUNC_EN_USBD | R92C_SYS_FUNC_EN_USBA |
> +	    R92C_SYS_FUNC_EN_BB_GLB_RST);
> +	urtwn_write_1(sc, R92C_SYS_FUNC_EN,
> +	    R92C_SYS_FUNC_EN_USBD | R92C_SYS_FUNC_EN_USBA);
> +
> +	/* Reset digital sequence. */
> +	if (urtwn_read_1(sc, R92C_MCUFWDL) & R92C_MCUFWDL_RDY) {
> +		/* Reset MCU ready status. */
> +		urtwn_write_1(sc, R92C_MCUFWDL, 0);
> +		/* If firmware in ram code, do reset. */
> +		rtwn_fw_reset(&sc->sc_sc);
> +	}
> +
> +	/* Reset MAC and enable 8051. */
> +	urtwn_write_1(sc, R92C_SYS_FUNC_EN + 1,
> +	    (R92C_SYS_FUNC_EN_CPUEN | R92C_SYS_FUNC_EN_ELDR |
> +	    R92C_SYS_FUNC_EN_HWPDN) >> 8);
> +
> +	/* Reset MCU ready status. */
> +	urtwn_write_1(sc, R92C_MCUFWDL, 0);
> +
> +	/* Disable MAC clock. */
> +	urtwn_write_2(sc, R92C_SYS_CLKR,
> +	    R92C_SYS_CLKR_ANAD16V_EN | R92C_SYS_CLKR_ANA8M |
> +	    R92C_SYS_CLKR_LOADER_EN | R92C_SYS_CLKR_80M_SSC_DIS |
> +	    R92C_SYS_CLKR_SYS_EN | R92C_SYS_CLKR_RING_EN | 0x4000);
> +
> +	/* Disable AFE PLL. */
> +	urtwn_write_1(sc, R92C_AFE_PLL_CTRL, 0x80);
> +
> +	/* Gated AFE DIG_CLOCK. */
> +	urtwn_write_2(sc, R92C_AFE_XTAL_CTRL, 0x880f);
> +
> +	/* Isolated digital to PON. */

what does "Isolated digital to PON" mean?

> +	urtwn_write_1(sc, R92C_SYS_ISO_CTRL,
> +	    R92C_SYS_ISO_CTRL_MD2PP | R92C_SYS_ISO_CTRL_PA2PCIE |
> +	    R92C_SYS_ISO_CTRL_PD2CORE | R92C_SYS_ISO_CTRL_IP2MAC |
> +	    R92C_SYS_ISO_CTRL_DIOP | R92C_SYS_ISO_CTRL_DIOE);
> +
> +	/* Pull GPIO pins to balance level and LED control. */
> +	/* 1. Disable GPIO[7:0]. */

what do these GPIOs do?

> +	urtwn_write_2(sc, R92C_GPIO_IOSEL, 0x0000);
> +	reg = urtwn_read_4(sc, R92C_GPIO_PIN_CTRL) & ~0x0000ff00;
> +	reg |= ((reg << 8) & 0x0000ff00) | 0x00ff0000;
> +	urtwn_write_4(sc, R92C_GPIO_PIN_CTRL, reg);
> +
> +	/* 2. Disable GPIO[10:8]. */

what do these GPIOs do?

> +	urtwn_write_1(sc, R92C_MAC_PINMUX_CFG, 0x00);
> +	reg = urtwn_read_2(sc, R92C_GPIO_IO_SEL) & ~0x00f0;
> +	reg |= (((reg & 0x000f) << 4) | 0x0780);
> +	urtwn_write_2(sc, R92C_GPIO_IO_SEL, reg);
> +
> +	/* 3. Disable LED0 & 1. */
> +	urtwn_write_2(sc, R92C_LEDCFG0, 0x8080);
> +
> +	/* Reset digital sequence: disable ELDR clock. */
> +	urtwn_write_2(sc, R92C_SYS_CLKR,
> +	    R92C_SYS_CLKR_ANAD16V_EN | R92C_SYS_CLKR_ANA8M |
> +	    R92C_SYS_CLKR_LOADER_EN | R92C_SYS_CLKR_80M_SSC_DIS |
> +	    R92C_SYS_CLKR_SYS_EN | R92C_SYS_CLKR_RING_EN | 0x4000);
> +
> +	/* Isolated ELDR to PON. */

what does this mean?

> +	urtwn_write_1(sc, R92C_SYS_ISO_CTRL + 1,
> +	    (R92C_SYS_ISO_CTRL_DIOR | R92C_SYS_ISO_CTRL_PWC_EV12V) >> 8);
> +
> +	/* Disable analog sequence: disable A15 power. */
> +	urtwn_write_1(sc, R92C_LDOA15_CTRL, R92C_LDOA15_CTRL_OBUF);
> +	/* Disable digital core power. */
> +	urtwn_write_1(sc, R92C_LDOV12D_CTRL,
> +	    urtwn_read_1(sc, R92C_LDOV12D_CTRL) &
> +	    ~R92C_LDOV12D_CTRL_LDV12_EN);
> +
> +	/* Enter PFM mode. */

what does this mean?

> +	urtwn_write_1(sc, R92C_SPS0_CTRL, 0x23);
> +
> +	/* Set USB suspend. */
> +	urtwn_write_2(sc, R92C_APS_FSMCO,
> +	    R92C_APS_FSMCO_APDM_HOST | R92C_APS_FSMCO_AFSM_HSUS |
> +	    R92C_APS_FSMCO_PFM_ALDN);
> +
> +	/* Lock ISO/CLK/Power control register. */
> +	urtwn_write_1(sc, R92C_RSV_CTRL, 0x0e);
>  }
>
>  int
>
>
> --
> Regards,
>  Mikolaj
>