Clear the "persistence bit" on iwm(4) 9k devices during hardware init.
authorstsp <stsp@openbsd.org>
Fri, 9 Jul 2021 11:04:05 +0000 (11:04 +0000)
committerstsp <stsp@openbsd.org>
Fri, 9 Jul 2021 11:04:05 +0000 (11:04 +0000)
According to iwlwifi commit messages this fixes an edge case where
9k family devices fail to resume after system suspend.
See Linux commit 8954e1eb2270fa2effffd031b4839253952c76f2

sys/dev/pci/if_iwm.c

index 40a03cb..412e6ef 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: if_iwm.c,v 1.346 2021/07/09 10:46:56 stsp Exp $       */
+/*     $OpenBSD: if_iwm.c,v 1.347 2021/07/09 11:04:05 stsp Exp $       */
 
 /*
  * Copyright (c) 2014, 2016 genua gmbh <info@genua.de>
@@ -254,7 +254,9 @@ int iwm_firmware_store_section(struct iwm_softc *, enum iwm_ucode_type,
 int    iwm_set_default_calib(struct iwm_softc *, const void *);
 void   iwm_fw_info_free(struct iwm_fw_info *);
 int    iwm_read_firmware(struct iwm_softc *, enum iwm_ucode_type);
+uint32_t iwm_read_prph_unlocked(struct iwm_softc *, uint32_t);
 uint32_t iwm_read_prph(struct iwm_softc *, uint32_t);
+void   iwm_write_prph_unlocked(struct iwm_softc *, uint32_t, uint32_t);
 void   iwm_write_prph(struct iwm_softc *, uint32_t, uint32_t);
 int    iwm_read_mem(struct iwm_softc *, uint32_t, void *, int);
 int    iwm_write_mem(struct iwm_softc *, uint32_t, const void *, int);
@@ -292,6 +294,7 @@ void        iwm_apm_stop(struct iwm_softc *);
 int    iwm_allow_mcast(struct iwm_softc *);
 void   iwm_init_msix_hw(struct iwm_softc *);
 void   iwm_conf_msix_hw(struct iwm_softc *, int);
+int    iwm_clear_persistence_bit(struct iwm_softc *);
 int    iwm_start_hw(struct iwm_softc *);
 void   iwm_stop_device(struct iwm_softc *);
 void   iwm_nic_config(struct iwm_softc *);
@@ -1006,25 +1009,37 @@ iwm_read_firmware(struct iwm_softc *sc, enum iwm_ucode_type ucode_type)
 }
 
 uint32_t
-iwm_read_prph(struct iwm_softc *sc, uint32_t addr)
+iwm_read_prph_unlocked(struct iwm_softc *sc, uint32_t addr)
 {
-       iwm_nic_assert_locked(sc);
        IWM_WRITE(sc,
            IWM_HBUS_TARG_PRPH_RADDR, ((addr & 0x000fffff) | (3 << 24)));
        IWM_BARRIER_READ_WRITE(sc);
        return IWM_READ(sc, IWM_HBUS_TARG_PRPH_RDAT);
 }
 
-void
-iwm_write_prph(struct iwm_softc *sc, uint32_t addr, uint32_t val)
+uint32_t
+iwm_read_prph(struct iwm_softc *sc, uint32_t addr)
 {
        iwm_nic_assert_locked(sc);
+       return iwm_read_prph_unlocked(sc, addr);
+}
+
+void
+iwm_write_prph_unlocked(struct iwm_softc *sc, uint32_t addr, uint32_t val)
+{
        IWM_WRITE(sc,
            IWM_HBUS_TARG_PRPH_WADDR, ((addr & 0x000fffff) | (3 << 24)));
        IWM_BARRIER_WRITE(sc);
        IWM_WRITE(sc, IWM_HBUS_TARG_PRPH_WDAT, val);
 }
 
+void
+iwm_write_prph(struct iwm_softc *sc, uint32_t addr, uint32_t val)
+{
+       iwm_nic_assert_locked(sc);
+       iwm_write_prph_unlocked(sc, addr, val);
+}
+
 void
 iwm_write_prph64(struct iwm_softc *sc, uint64_t addr, uint64_t val)
 {
@@ -1961,6 +1976,26 @@ iwm_conf_msix_hw(struct iwm_softc *sc, int stopped)
            IWM_MSIX_HW_INT_CAUSES_REG_HAP);
 }
 
+int
+iwm_clear_persistence_bit(struct iwm_softc *sc)
+{
+       uint32_t hpm, wprot;
+
+       hpm = iwm_read_prph_unlocked(sc, IWM_HPM_DEBUG);
+       if (hpm != 0xa5a5a5a0 && (hpm & IWM_HPM_PERSISTENCE_BIT)) {
+               wprot = iwm_read_prph_unlocked(sc, IWM_PREG_PRPH_WPROT_9000);
+               if (wprot & IWM_PREG_WFPM_ACCESS) {
+                       printf("%s: cannot clear persistence bit\n",
+                           DEVNAME(sc));
+                       return EPERM;
+               }
+               iwm_write_prph_unlocked(sc, IWM_HPM_DEBUG,
+                   hpm & ~IWM_HPM_PERSISTENCE_BIT);
+       }
+
+       return 0;
+}
+
 int
 iwm_start_hw(struct iwm_softc *sc)
 {
@@ -1970,6 +2005,12 @@ iwm_start_hw(struct iwm_softc *sc)
        if (err)
                return err;
 
+       if (sc->sc_device_family == IWM_DEVICE_FAMILY_9000) {
+               err = iwm_clear_persistence_bit(sc);
+               if (err)
+                       return err;
+       }
+
        /* Reset the entire device */
        IWM_WRITE(sc, IWM_CSR_RESET, IWM_CSR_RESET_REG_FLAG_SW_RESET);
        DELAY(5000);