Add support for 40MHz channels to the iwn(4) driver.
authorstsp <stsp@openbsd.org>
Fri, 12 Nov 2021 11:41:04 +0000 (11:41 +0000)
committerstsp <stsp@openbsd.org>
Fri, 12 Nov 2021 11:41:04 +0000 (11:41 +0000)
Tested:
4965: jsg
5300: stsp, Jan Stary
6030: Fred Crowsons
6200: stsp
6205: stsp, Josh Grosse
6300: okan, afresh1

sys/dev/pci/if_iwn.c
sys/dev/pci/if_iwnreg.h
sys/dev/pci/if_iwnvar.h

index 0ca26cf..2639a6c 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: if_iwn.c,v 1.252 2021/11/12 11:40:35 stsp Exp $       */
+/*     $OpenBSD: if_iwn.c,v 1.253 2021/11/12 11:41:04 stsp Exp $       */
 
 /*-
  * Copyright (c) 2007-2010 Damien Bergamini <damien.bergamini@free.fr>
@@ -241,12 +241,16 @@ uint16_t  iwn_get_passive_dwell_time(struct iwn_softc *, uint16_t);
 int            iwn_scan(struct iwn_softc *, uint16_t, int);
 void           iwn_scan_abort(struct iwn_softc *);
 int            iwn_bgscan(struct ieee80211com *);
+void           iwn_rxon_configure_ht40(struct ieee80211com *,
+                   struct ieee80211_node *);
+int            iwn_rxon_ht40_enabled(struct iwn_softc *);
 int            iwn_auth(struct iwn_softc *, int);
 int            iwn_run(struct iwn_softc *);
 int            iwn_set_key(struct ieee80211com *, struct ieee80211_node *,
                    struct ieee80211_key *);
 void           iwn_delete_key(struct ieee80211com *, struct ieee80211_node *,
                    struct ieee80211_key *);
+void           iwn_updatechan(struct ieee80211com *);
 void           iwn_updateprot(struct ieee80211com *);
 void           iwn_updateslot(struct ieee80211com *);
 void           iwn_update_rxon_restore_power(struct iwn_softc *);
@@ -491,13 +495,15 @@ iwn_attach(struct device *parent, struct device *self, void *aux)
                ic->ic_caps |= (IEEE80211_C_QOS | IEEE80211_C_TX_AMPDU);
                /* Set HT capabilities. */
                ic->ic_htcaps = IEEE80211_HTCAP_SGI20;
+               /* 6200 devices have issues with SGI40 for some reason. */
+               if ((sc->sc_flags & IWN_FLAG_INTERNAL_PA) == 0)
+                       ic->ic_htcaps |= IEEE80211_HTCAP_SGI40;
+               ic->ic_htcaps |= IEEE80211_HTCAP_CBW20_40;
 #ifdef notyet
                ic->ic_htcaps |=
 #if IWN_RBUF_SIZE == 8192
                    IEEE80211_HTCAP_AMSDU7935 |
 #endif
-                   IEEE80211_HTCAP_CBW20_40 |
-                   IEEE80211_HTCAP_SGI40;
                if (sc->hw_type != IWN_HW_REV_TYPE_4965)
                        ic->ic_htcaps |= IEEE80211_HTCAP_GF;
                if (sc->hw_type == IWN_HW_REV_TYPE_6050)
@@ -543,6 +549,7 @@ iwn_attach(struct device *parent, struct device *self, void *aux)
        ic->ic_updateedca = iwn_updateedca;
        ic->ic_set_key = iwn_set_key;
        ic->ic_delete_key = iwn_delete_key;
+       ic->ic_updatechan = iwn_updatechan;
        ic->ic_updateprot = iwn_updateprot;
        ic->ic_updateslot = iwn_updateslot;
        ic->ic_ampdu_rx_start = iwn_ampdu_rx_start;
@@ -1477,8 +1484,8 @@ iwn4965_read_eeprom(struct iwn_softc *sc)
        /* Read regulatory domain (4 ASCII characters). */
        iwn_read_prom_data(sc, IWN4965_EEPROM_DOMAIN, sc->eeprom_domain, 4);
 
-       /* Read the list of authorized channels (20MHz ones only). */
-       for (i = 0; i < 5; i++) {
+       /* Read the list of authorized channels. */
+       for (i = 0; i < 7; i++) {
                addr = iwn4965_regulatory_bands[i];
                iwn_read_eeprom_channels(sc, i, addr);
        }
@@ -1562,8 +1569,8 @@ iwn5000_read_eeprom(struct iwn_softc *sc)
        iwn_read_prom_data(sc, base + IWN5000_EEPROM_DOMAIN,
            sc->eeprom_domain, 4);
 
-       /* Read the list of authorized channels (20MHz ones only). */
-       for (i = 0; i < 5; i++) {
+       /* Read the list of authorized channels. */
+       for (i = 0; i < 7; i++) {
                addr = base + iwn5000_regulatory_bands[i];
                iwn_read_eeprom_channels(sc, i, addr);
        }
@@ -1633,7 +1640,7 @@ iwn_read_eeprom_channels(struct iwn_softc *sc, int n, uint32_t addr)
                            IEEE80211_CHAN_CCK | IEEE80211_CHAN_OFDM |
                            IEEE80211_CHAN_DYN | IEEE80211_CHAN_2GHZ;
 
-               } else {        /* 5GHz band */
+               } else if (n < 5) {     /* 5GHz band */
                        /*
                         * Some adapters support channels 7, 8, 11 and 12
                         * both in the 2GHz and 4.9GHz bands.
@@ -1648,22 +1655,29 @@ iwn_read_eeprom_channels(struct iwn_softc *sc, int n, uint32_t addr)
                        ic->ic_channels[chan].ic_flags = IEEE80211_CHAN_A;
                        /* We have at least one valid 5GHz channel. */
                        sc->sc_flags |= IWN_FLAG_HAS_5GHZ;
+               } else  { /* 40 MHz */
+                       sc->maxpwr40[chan] = channels[i].maxpwr;
+                       ic->ic_channels[chan].ic_flags |= IEEE80211_CHAN_40MHZ;
                }
 
-               /* Is active scan allowed on this channel? */
-               if (!(channels[i].flags & IWN_EEPROM_CHAN_ACTIVE)) {
-                       ic->ic_channels[chan].ic_flags |=
-                           IEEE80211_CHAN_PASSIVE;
-               }
+               if (n < 5) {
+                       /* Is active scan allowed on this channel? */
+                       if (!(channels[i].flags & IWN_EEPROM_CHAN_ACTIVE)) {
+                               ic->ic_channels[chan].ic_flags |=
+                                   IEEE80211_CHAN_PASSIVE;
+                       }
 
-               /* Save maximum allowed TX power for this channel. */
-               sc->maxpwr[chan] = channels[i].maxpwr;
+                       /* Save maximum allowed TX power for this channel. */
+                       sc->maxpwr[chan] = channels[i].maxpwr;
 
-               if (sc->sc_flags & IWN_FLAG_HAS_11N)
-                       ic->ic_channels[chan].ic_flags |= IEEE80211_CHAN_HT;
+                       if (sc->sc_flags & IWN_FLAG_HAS_11N)
+                               ic->ic_channels[chan].ic_flags |=
+                                   IEEE80211_CHAN_HT;
+               }
 
-               DPRINTF(("adding chan %d flags=0x%x maxpwr=%d\n",
-                   chan, channels[i].flags, sc->maxpwr[chan]));
+               DPRINTF(("adding chan %d flags=0x%x maxpwr=%d maxpwr40=%d\n",
+                   chan, channels[i].flags, sc->maxpwr[chan],
+                   sc->maxpwr40[chan]));
        }
 }
 
@@ -2287,8 +2301,9 @@ iwn_ht_single_rate_control(struct iwn_softc *sc, struct ieee80211_node *ni,
        struct iwn_node *wn = (void *)ni;
        int mcs = rate;
        const struct ieee80211_ht_rateset *rs =
-           ieee80211_ra_get_ht_rateset(rate, 0 /* chan40 */,
-           ieee80211_ra_use_ht_sgi(ni));
+           ieee80211_ra_get_ht_rateset(rate,
+               ieee80211_node_supports_ht_chan40(ni),
+               ieee80211_ra_use_ht_sgi(ni));
        unsigned int retries = 0, i;
 
        /*
@@ -2488,6 +2503,8 @@ iwn_rx_statistics(struct iwn_softc *sc, struct iwn_rx_desc *desc,
        DPRINTFN(3, ("received statistics (cmd=%d)\n", desc->type));
        sc->calib_cnt = 0;      /* Reset TX power calibration timeout. */
 
+       sc->rx_stats_flags = htole32(stats->flags);
+
        /* Test if temperature has changed. */
        if (stats->general.temp != sc->rawtemp) {
                /* Convert "raw" temperature to degC. */
@@ -3537,7 +3554,9 @@ iwn_tx(struct iwn_softc *sc, struct mbuf *m, struct ieee80211_node *ni)
        if ((ni->ni_flags & IEEE80211_NODE_HT) &&
            tx->id != sc->broadcast_id) {
                tx->rflags = rinfo->ht_flags;
-               if (ni->ni_htcaps & IEEE80211_HTCAP_SGI20)
+               if (iwn_rxon_ht40_enabled(sc))
+                       tx->rflags |= IWN_RFLAG_HT40;
+               if (ieee80211_ra_use_ht_sgi(ni))
                        tx->rflags |= IWN_RFLAG_SGI;
        }
        else
@@ -3925,12 +3944,17 @@ iwn_set_link_quality(struct iwn_softc *sc, struct ieee80211_node *ni)
                        linkq.retry[i].plcp = rinfo->ht_plcp;
                        linkq.retry[i].rflags = rinfo->ht_flags;
 
-                       if (ni->ni_htcaps & IEEE80211_HTCAP_SGI20)
-                               linkq.retry[i].rflags |= IWN_RFLAG_SGI;
-
                        /* XXX set correct ant mask for MIMO rates here */
                        linkq.retry[i].rflags |= IWN_RFLAG_ANT(txant);
 
+                       /* First two Tx attempts may use 40MHz/SGI. */
+                       if (i < 2) {
+                               if (iwn_rxon_ht40_enabled(sc))
+                                       linkq.retry[i].rflags |= IWN_RFLAG_HT40;
+                               if (ieee80211_ra_use_ht_sgi(ni))
+                                       linkq.retry[i].rflags |= IWN_RFLAG_SGI;
+                       }
+
                        if (++i >= IWN_MAX_TX_RETRIES)
                                break;
                }
@@ -4121,8 +4145,8 @@ iwn4965_set_txpower(struct iwn_softc *sc, int async)
        struct iwn4965_eeprom_chan_samples *chans;
        const uint8_t *rf_gain, *dsp_gain;
        int32_t vdiff, tdiff;
-       int i, c, grp, maxpwr;
-       uint8_t chan;
+       int i, c, grp, maxpwr, is_ht40 = 0;
+       uint8_t chan, ext_chan;
 
        /* Retrieve current channel from last RXON. */
        chan = sc->rxon.chan;
@@ -4175,17 +4199,26 @@ iwn4965_set_txpower(struct iwn_softc *sc, int async)
        chans = sc->bands[i].chans;
        DPRINTF(("chan %d sub-band=%d\n", chan, i));
 
+       if (iwn_rxon_ht40_enabled(sc)) {
+               is_ht40 = 1;
+               if (le32toh(sc->rxon.flags) & IWN_RXON_HT_HT40MINUS)
+                       ext_chan = chan - 2;
+               else
+                       ext_chan = chan + 2;
+       } else
+               ext_chan = chan;
+
        for (c = 0; c < 2; c++) {
                uint8_t power, gain, temp;
                int maxchpwr, pwr, ridx, idx;
 
-               power = interpolate(chan,
+               power = interpolate(ext_chan,
                    chans[0].num, chans[0].samples[c][1].power,
                    chans[1].num, chans[1].samples[c][1].power, 1);
-               gain  = interpolate(chan,
+               gain  = interpolate(ext_chan,
                    chans[0].num, chans[0].samples[c][1].gain,
                    chans[1].num, chans[1].samples[c][1].gain, 1);
-               temp  = interpolate(chan,
+               temp  = interpolate(ext_chan,
                    chans[0].num, chans[0].samples[c][1].temp,
                    chans[1].num, chans[1].samples[c][1].temp, 1);
                DPRINTF(("TX chain %d: power=%d gain=%d temp=%d\n",
@@ -4198,7 +4231,10 @@ iwn4965_set_txpower(struct iwn_softc *sc, int async)
 
                for (ridx = 0; ridx <= IWN_RIDX_MAX; ridx++) {
                        /* Convert dBm to half-dBm. */
-                       maxchpwr = sc->maxpwr[chan] * 2;
+                       if (is_ht40)
+                               maxchpwr = sc->maxpwr40[chan] * 2;
+                       else
+                               maxchpwr = sc->maxpwr[chan] * 2;
 #ifdef notyet
                        if (ridx > iwn_mcs2ridx[7] && ridx < iwn_mcs2ridx[16])
                                maxchpwr -= 6;  /* MIMO 2T: -3dB */
@@ -4334,9 +4370,15 @@ iwn4965_get_temperature(struct iwn_softc *sc)
        struct iwn_ucode_info *uc = &sc->ucode_info;
        int32_t r1, r2, r3, r4, temp;
 
-       r1 = letoh32(uc->temp[0].chan20MHz);
-       r2 = letoh32(uc->temp[1].chan20MHz);
-       r3 = letoh32(uc->temp[2].chan20MHz);
+       if (sc->rx_stats_flags & IWN_STATS_FLAGS_BAND_HT40) {
+               r1 = letoh32(uc->temp[0].chan40MHz);
+               r2 = letoh32(uc->temp[1].chan40MHz);
+               r3 = letoh32(uc->temp[2].chan40MHz);
+       } else {
+               r1 = letoh32(uc->temp[0].chan20MHz);
+               r2 = letoh32(uc->temp[1].chan20MHz);
+               r3 = letoh32(uc->temp[2].chan20MHz);
+       }
        r4 = letoh32(sc->rawtemp);
 
        if (r1 == r3)   /* Prevents division by 0 (should not happen). */
@@ -5375,6 +5417,37 @@ iwn_bgscan(struct ieee80211com *ic)
        return error;
 }
 
+void
+iwn_rxon_configure_ht40(struct ieee80211com *ic, struct ieee80211_node *ni)
+{
+       struct iwn_softc *sc = ic->ic_softc;
+       uint8_t sco = (ni->ni_htop0 & IEEE80211_HTOP0_SCO_MASK);
+       enum ieee80211_htprot htprot = (ni->ni_htop1 &
+           IEEE80211_HTOP1_PROT_MASK);
+
+       sc->rxon.flags &= ~htole32(IWN_RXON_HT_CHANMODE_MIXED2040 |
+           IWN_RXON_HT_CHANMODE_PURE40 | IWN_RXON_HT_HT40MINUS);
+
+       if (ieee80211_node_supports_ht_chan40(ni) &&
+           (sco == IEEE80211_HTOP0_SCO_SCA ||
+           sco == IEEE80211_HTOP0_SCO_SCB)) {
+               if (sco == IEEE80211_HTOP0_SCO_SCB)
+                       sc->rxon.flags |= htole32(IWN_RXON_HT_HT40MINUS);
+               if (htprot == IEEE80211_HTPROT_20MHZ)
+                       sc->rxon.flags |= htole32(IWN_RXON_HT_CHANMODE_PURE40);
+               else
+                       sc->rxon.flags |= htole32(
+                           IWN_RXON_HT_CHANMODE_MIXED2040);
+       }
+}
+
+int
+iwn_rxon_ht40_enabled(struct iwn_softc *sc)
+{
+       return ((le32toh(sc->rxon.flags) & IWN_RXON_HT_CHANMODE_MIXED2040) ||
+           (le32toh(sc->rxon.flags) & IWN_RXON_HT_CHANMODE_PURE40)) ? 1 : 0;
+}
+
 int
 iwn_auth(struct iwn_softc *sc, int arg)
 {
@@ -5418,6 +5491,8 @@ iwn_auth(struct iwn_softc *sc, int arg)
                sc->rxon.cck_mask  = 0x0f;
                sc->rxon.ofdm_mask = 0x15;
        }
+       /* Configure 40MHz early to avoid problems on 6205 devices. */
+       iwn_rxon_configure_ht40(ic, ni);
        DPRINTF(("%s: rxon chan %d flags %x cck %x ofdm %x\n", __func__,
            sc->rxon.chan, le32toh(sc->rxon.flags), sc->rxon.cck_mask,
            sc->rxon.ofdm_mask));
@@ -5503,6 +5578,8 @@ iwn_run(struct iwn_softc *sc)
        } else
                sc->rxon.flags &= ~htole32(IWN_RXON_HT_PROTMODE(3));
 
+       iwn_rxon_configure_ht40(ic, ni);
+
        if (IEEE80211_IS_CHAN_5GHZ(ni->ni_chan)) {
                /* 11a or 11n 5GHz */
                sc->rxon.cck_mask  = 0;
@@ -5554,6 +5631,8 @@ iwn_run(struct iwn_softc *sc)
                        (ic->ic_ampdu_params & IEEE80211_AMPDU_PARAM_LE)) |
                    IWN_AMDPU_DENSITY(
                        (ic->ic_ampdu_params & IEEE80211_AMPDU_PARAM_SS) >> 2));
+               if (iwn_rxon_ht40_enabled(sc))
+                       node.htflags |= htole32(IWN_40MHZ_ENABLE);
        }
        DPRINTF(("adding BSS node\n"));
        error = ops->add_node(sc, &node, 1);
@@ -5651,6 +5730,19 @@ iwn_delete_key(struct ieee80211com *ic, struct ieee80211_node *ni,
        (void)ops->add_node(sc, &node, 1);
 }
 
+void
+iwn_updatechan(struct ieee80211com *ic)
+{
+       struct iwn_softc *sc = ic->ic_softc;
+
+       if (ic->ic_state != IEEE80211_S_RUN)
+               return;
+
+       iwn_rxon_configure_ht40(ic, ic->ic_bss);
+       sc->ops.update_rxon(sc);
+       iwn_set_link_quality(sc, ic->ic_bss);
+}
+
 void
 iwn_updateprot(struct ieee80211com *ic)
 {
index a6ed773..110efd3 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: if_iwnreg.h,v 1.58 2021/11/12 11:40:36 stsp Exp $     */
+/*     $OpenBSD: if_iwnreg.h,v 1.59 2021/11/12 11:41:04 stsp Exp $     */
 
 /*-
  * Copyright (c) 2007, 2008
@@ -637,6 +637,8 @@ struct iwn_node_info {
        uint32_t        htflags;
 #define IWN_AMDPU_SIZE_FACTOR(x)       ((x) << 19)
 #define IWN_AMDPU_SIZE_FACTOR_MASK     ((0x3) << 19)
+#define IWN_40MHZ_ENABLE               (1 << 21)
+#define IWN_MIMO_DISABLE               (1 << 22)
 #define IWN_AMDPU_DENSITY(x)           ((x) << 23)
 #define IWN_AMDPU_DENSITY_MASK         ((0x7) << 23)
 
@@ -1537,6 +1539,8 @@ struct iwn_general_stats {
 
 struct iwn_stats {
        uint32_t                        flags;
+#define IWN_STATS_FLAGS_BAND_24G       0x02
+#define IWN_STATS_FLAGS_BAND_HT40      0x08
        struct iwn_rx_stats             rx;
        struct iwn_tx_stats             tx;
        struct iwn_general_stats        general;
index dc0534d..f022488 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: if_iwnvar.h,v 1.41 2021/11/11 13:36:58 stsp Exp $     */
+/*     $OpenBSD: if_iwnvar.h,v 1.42 2021/11/12 11:41:04 stsp Exp $     */
 
 /*-
  * Copyright (c) 2007, 2008
@@ -280,6 +280,7 @@ struct iwn_softc {
 #define IWN_LAST_RX_AMPDU      0x02
        struct iwn_ucode_info   ucode_info;
        struct iwn_rxon         rxon;
+       uint32_t                rx_stats_flags;
        uint32_t                rawtemp;
        int                     temp;
        int                     noise;
@@ -298,6 +299,7 @@ struct iwn_softc {
        int8_t                  maxpwr2GHz;
        int8_t                  maxpwr5GHz;
        int8_t                  maxpwr[IEEE80211_CHAN_MAX];
+       int8_t                  maxpwr40[IEEE80211_CHAN_MAX];
        int8_t                  enh_maxpwr[35];
 
        uint8_t                 reset_noise_gain;