This patch adds 802.11n 40MHz support to the iwn(4) driver.
This driver supports many different devices. Please try to be precise
about which device you have tested so I can maintain a record of our
test coverage.
I have tested on a 6205 device. More tests are needed, especially on
the old 4965AGN generation because those chips require the driver to
do specific calibration work which newer chips perform in firmware.
I suspect you will find 4965 devices in older thinkpads which first
introduced 11n wifi to these laptops (the x60/x61 generation probably).
Because iwn(4) does not support MIMO yet, this pushes maximum throughput
from about 50 Mbit/s up to about 100 Mbit/s. Adding MIMO support would
probably double the speed again, but that is left for the future.
To check whether your access point uses a 40MHz channel, run this command
while associated to the access point:
tcpdump -n -i iwm0 -v -y IEEE802_11_RADIO -s 4096 type mgt and subtype beacon
The htop information element displayed by this command includes the
channel width. A 40MHz channel looks like this: htop=<40MHz chan X:Y ....>
diff refs/heads/master refs/heads/40mhz
blob - aff55acd0f19afbd39ffc78596af5349df58468f
blob + 853274d96496ec16fcf7d876f7922470cbd7bc17
--- sys/dev/pci/if_iwn.c
+++ sys/dev/pci/if_iwn.c
@@ -241,12 +241,16 @@ uint16_t iwn_get_passive_dwell_time(struct iwn_softc *
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(struct iwn_softc *);
@@ -488,14 +492,13 @@ iwn_attach(struct device *parent, struct device *self,
if (sc->sc_flags & IWN_FLAG_HAS_11N) {
ic->ic_caps |= (IEEE80211_C_QOS | IEEE80211_C_TX_AMPDU);
/* Set HT capabilities. */
- ic->ic_htcaps = IEEE80211_HTCAP_SGI20;
+ ic->ic_htcaps = IEEE80211_HTCAP_SGI20 | 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)
@@ -541,6 +544,7 @@ iwn_attach(struct device *parent, struct device *self,
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;
@@ -1473,8 +1477,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);
}
@@ -1558,8 +1562,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);
}
@@ -1629,7 +1633,7 @@ iwn_read_eeprom_channels(struct iwn_softc *sc, int n,
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.
@@ -1644,22 +1648,29 @@ iwn_read_eeprom_channels(struct iwn_softc *sc, int n,
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]));
}
}
@@ -2484,6 +2495,8 @@ iwn_rx_statistics(struct iwn_softc *sc, struct iwn_rx_
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. */
@@ -3921,12 +3934,18 @@ iwn_set_link_quality(struct iwn_softc *sc, struct ieee
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 && iwn_rxon_ht40_enabled(sc)) {
+ linkq.retry[i].rflags |= IWN_RFLAG_HT40;
+ if (ni->ni_htcaps & IEEE80211_HTCAP_SGI40)
+ linkq.retry[i].rflags |= IWN_RFLAG_SGI;
+ } else if (i < 2 &&
+ (ni->ni_htcaps & IEEE80211_HTCAP_SGI20))
+ linkq.retry[i].rflags |= IWN_RFLAG_SGI;
+
if (++i >= IWN_MAX_TX_RETRIES)
break;
}
@@ -4117,8 +4136,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;
@@ -4171,17 +4190,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",
@@ -4194,7 +4222,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 */
@@ -4330,9 +4361,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). */
@@ -5371,7 +5408,38 @@ 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)
{
struct iwn_ops *ops = &sc->ops;
@@ -5414,6 +5482,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));
@@ -5499,6 +5569,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;
@@ -5550,6 +5622,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 (ieee80211_node_supports_ht_chan40(ni))
+ node.htflags |= htole32(IWN_40MHZ_ENABLE);
}
DPRINTF(("adding BSS node\n"));
error = ops->add_node(sc, &node, 1);
@@ -5648,6 +5722,19 @@ iwn_delete_key(struct ieee80211com *ic, struct ieee802
}
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);
+ iwn_update_rxon(sc);
+ iwn_set_link_quality(sc, ic->ic_bss);
+}
+
+void
iwn_updateprot(struct ieee80211com *ic)
{
struct iwn_softc *sc = ic->ic_softc;
blob - a8fc8504b7fc429bd927d992c75fd56f2641a4ce
blob + ee29801723815444371ead88254fa0fe5bc5c2f7
--- sys/dev/pci/if_iwnreg.h
+++ sys/dev/pci/if_iwnreg.h
@@ -626,6 +626,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)
@@ -1526,6 +1528,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;
blob - e1c3e196d28b14893fa85f94dba02f8357d5aaca
blob + 89c2826340b5d4f80072866a1f215d5b434bf3a3
--- sys/dev/pci/if_iwnvar.h
+++ sys/dev/pci/if_iwnvar.h
@@ -279,6 +279,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;
@@ -297,6 +298,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;