From: Mike Larkin Subject: Re: urtwn: power the RTL8188CU/8192CU off in urtwn_stop() To: Mikolaj Kucharski Cc: tech@openbsd.org Date: Fri, 30 Oct 2026 02:00:56 -0700 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 >