diff options
author | Stefan Sperling <stsp@cvs.openbsd.org> | 2020-05-18 17:56:42 +0000 |
---|---|---|
committer | Stefan Sperling <stsp@cvs.openbsd.org> | 2020-05-18 17:56:42 +0000 |
commit | 882688f7856767e81889b1afd40bb64dc4cf5951 (patch) | |
tree | fe8a534cc1ed57d522eae8f2974b2d712fcb7995 | |
parent | 05b873b2e11f541fd3b1afefbe2dbcfab36fe87a (diff) |
Offload CCMP (WPA2) encryption and decryption to iwm(4) hardware.
This reduces CPU load during traffic bursts.
Based on an older diff we had in CVS history, updated to work with
newer firmware versions we use today.
Tested on the following devices, with great support from several people:
3160 (stsp)
3168 (stsp)
7260 (jmatthew, tobhe)
7265 (stsp, procter, gonzalo)
8265 (benno, tb)
8260 (stsp, Liberto on bsd.network)
9260 (stsp, Jesper Wallin)
9560 (stsp)
Not tested: 3165; these run the same firmware as 7265 so will likely be fine.
I couldn't find a 3165 device in my pile.
ok jmatthew@ (who also found a small bug in my diff)
-rw-r--r-- | sys/dev/pci/if_iwm.c | 272 | ||||
-rw-r--r-- | sys/dev/pci/if_iwmreg.h | 46 |
2 files changed, 289 insertions, 29 deletions
diff --git a/sys/dev/pci/if_iwm.c b/sys/dev/pci/if_iwm.c index 906e910b02a..acbd2a5b3e9 100644 --- a/sys/dev/pci/if_iwm.c +++ b/sys/dev/pci/if_iwm.c @@ -1,4 +1,4 @@ -/* $OpenBSD: if_iwm.c,v 1.308 2020/04/29 13:13:30 stsp Exp $ */ +/* $OpenBSD: if_iwm.c,v 1.309 2020/05/18 17:56:41 stsp Exp $ */ /* * Copyright (c) 2014, 2016 genua gmbh <info@genua.de> @@ -372,8 +372,10 @@ int iwm_rxmq_get_signal_strength(struct iwm_softc *, struct iwm_rx_mpdu_desc *); void iwm_rx_rx_phy_cmd(struct iwm_softc *, struct iwm_rx_packet *, struct iwm_rx_data *); int iwm_get_noise(const struct iwm_statistics_rx_non_phy *); -void iwm_rx_frame(struct iwm_softc *, struct mbuf *, int, int, int, uint32_t, - struct ieee80211_rxinfo *, struct mbuf_list *); +int iwm_ccmp_decap(struct iwm_softc *, struct mbuf *, + struct ieee80211_node *); +void iwm_rx_frame(struct iwm_softc *, struct mbuf *, int, uint32_t, int, int, + uint32_t, struct ieee80211_rxinfo *, struct mbuf_list *); void iwm_rx_tx_cmd_single(struct iwm_softc *, struct iwm_rx_packet *, struct iwm_node *, int, int); void iwm_rx_tx_cmd(struct iwm_softc *, struct iwm_rx_packet *, @@ -452,6 +454,14 @@ int iwm_disassoc(struct iwm_softc *); int iwm_run(struct iwm_softc *); int iwm_run_stop(struct iwm_softc *); struct ieee80211_node *iwm_node_alloc(struct ieee80211com *); +int iwm_set_key_v1(struct ieee80211com *, struct ieee80211_node *, + struct ieee80211_key *); +int iwm_set_key(struct ieee80211com *, struct ieee80211_node *, + struct ieee80211_key *); +void iwm_delete_key_v1(struct ieee80211com *, + struct ieee80211_node *, struct ieee80211_key *); +void iwm_delete_key(struct ieee80211com *, + struct ieee80211_node *, struct ieee80211_key *); void iwm_calib_timeout(void *); void iwm_setrates(struct iwm_node *, int); int iwm_media_change(struct ifnet *); @@ -3896,16 +3906,65 @@ iwm_get_noise(const struct iwm_statistics_rx_non_phy *stats) return (nbant == 0) ? -127 : (total / nbant) - 107; } +int +iwm_ccmp_decap(struct iwm_softc *sc, struct mbuf *m, struct ieee80211_node *ni) +{ + struct ieee80211com *ic = &sc->sc_ic; + struct ieee80211_key *k = &ni->ni_pairwise_key; + struct ieee80211_frame *wh; + uint64_t pn, *prsc; + uint8_t *ivp; + uint8_t tid; + int hdrlen, hasqos; + + wh = mtod(m, struct ieee80211_frame *); + hdrlen = ieee80211_get_hdrlen(wh); + ivp = (uint8_t *)wh + hdrlen; + + /* Check that ExtIV bit is be set. */ + if (!(ivp[3] & IEEE80211_WEP_EXTIV)) + return 1; + + hasqos = ieee80211_has_qos(wh); + tid = hasqos ? ieee80211_get_qos(wh) & IEEE80211_QOS_TID : 0; + prsc = &k->k_rsc[tid]; + + /* Extract the 48-bit PN from the CCMP header. */ + pn = (uint64_t)ivp[0] | + (uint64_t)ivp[1] << 8 | + (uint64_t)ivp[4] << 16 | + (uint64_t)ivp[5] << 24 | + (uint64_t)ivp[6] << 32 | + (uint64_t)ivp[7] << 40; + if (pn <= *prsc) { + ic->ic_stats.is_ccmp_replays++; + return 1; + } + /* Last seen packet number is updated in ieee80211_inputm(). */ + + /* + * Some firmware versions strip the MIC, and some don't. It is not + * clear which of the capability flags could tell us what to expect. + * For now, keep things simple and just leave the MIC in place if + * it is present. + * + * The IV will be stripped by ieee80211_inputm(). + */ + return 0; +} + void iwm_rx_frame(struct iwm_softc *sc, struct mbuf *m, int chanidx, - int is_shortpre, int rate_n_flags, uint32_t device_timestamp, - struct ieee80211_rxinfo *rxi, struct mbuf_list *ml) + uint32_t rx_pkt_status, int is_shortpre, int rate_n_flags, + uint32_t device_timestamp, struct ieee80211_rxinfo *rxi, + struct mbuf_list *ml) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211_frame *wh; struct ieee80211_node *ni; struct ieee80211_channel *bss_chan; uint8_t saved_bssid[IEEE80211_ADDR_LEN] = { 0 }; + struct ifnet *ifp = IC2IFP(ic); if (chanidx < 0 || chanidx >= nitems(ic->ic_channels)) chanidx = ieee80211_chan2ieee(ic, ic->ic_ibss_chan); @@ -3922,6 +3981,38 @@ iwm_rx_frame(struct iwm_softc *sc, struct mbuf *m, int chanidx, } ni->ni_chan = &ic->ic_channels[chanidx]; + /* Handle hardware decryption. */ + if (((wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) != IEEE80211_FC0_TYPE_CTL) + && (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) && + !IEEE80211_IS_MULTICAST(wh->i_addr1) && + (ni->ni_flags & IEEE80211_NODE_RXPROT) && + ni->ni_pairwise_key.k_cipher == IEEE80211_CIPHER_CCMP) { + if ((rx_pkt_status & IWM_RX_MPDU_RES_STATUS_SEC_ENC_MSK) != + IWM_RX_MPDU_RES_STATUS_SEC_CCM_ENC) { + ic->ic_stats.is_ccmp_dec_errs++; + ifp->if_ierrors++; + m_freem(m); + return; + } + /* Check whether decryption was successful or not. */ + if ((rx_pkt_status & + (IWM_RX_MPDU_RES_STATUS_DEC_DONE | + IWM_RX_MPDU_RES_STATUS_MIC_OK)) != + (IWM_RX_MPDU_RES_STATUS_DEC_DONE | + IWM_RX_MPDU_RES_STATUS_MIC_OK)) { + ic->ic_stats.is_ccmp_dec_errs++; + ifp->if_ierrors++; + m_freem(m); + return; + } + if (iwm_ccmp_decap(sc, m, ni) != 0) { + ifp->if_ierrors++; + m_freem(m); + return; + } + rxi->rxi_flags |= IEEE80211_RXI_HWDEC; + } + #if NBPFILTER > 0 if (sc->sc_drvbpf != NULL) { struct iwm_rx_radiotap_header *tap = &sc->sc_rxtap; @@ -4046,7 +4137,7 @@ iwm_rx_mpdu(struct iwm_softc *sc, struct mbuf *m, void *pktdata, rxi.rxi_rssi = rssi; rxi.rxi_tstamp = device_timestamp; - iwm_rx_frame(sc, m, chanidx, + iwm_rx_frame(sc, m, chanidx, rx_pkt_status, (phy_flags & IWM_PHY_INFO_FLAG_SHPREAMBLE), rate_n_flags, device_timestamp, &rxi, ml); } @@ -4113,6 +4204,14 @@ iwm_rx_mpdu_mq(struct iwm_softc *sc, struct mbuf *m, void *pktdata, } } else hdrlen = ieee80211_get_hdrlen(wh); + + if ((le16toh(desc->status) & + IWM_RX_MPDU_RES_STATUS_SEC_ENC_MSK) == + IWM_RX_MPDU_RES_STATUS_SEC_CCM_ENC) { + /* Padding is inserted after the IV. */ + hdrlen += IEEE80211_CCMP_HDRLEN; + } + memmove(m->m_data + 2, m->m_data, hdrlen); m_adj(m, 2); } @@ -4130,7 +4229,7 @@ iwm_rx_mpdu_mq(struct iwm_softc *sc, struct mbuf *m, void *pktdata, rxi.rxi_rssi = rssi; rxi.rxi_tstamp = le64toh(desc->v1.tsf_on_air_rise); - iwm_rx_frame(sc, m, chanidx, + iwm_rx_frame(sc, m, chanidx, le16toh(desc->status), (phy_info & IWM_RX_MPDU_PHY_SHORT_PREAMBLE), rate_n_flags, device_timestamp, &rxi, ml); } @@ -4791,6 +4890,7 @@ iwm_tx(struct iwm_softc *sc, struct mbuf *m, struct ieee80211_node *ni, int ac) struct ieee80211_frame *wh; struct ieee80211_key *k = NULL; const struct iwm_rate *rinfo; + uint8_t *ivp; uint32_t flags; u_int hdrlen; bus_dma_segment_t *seg; @@ -4865,15 +4965,23 @@ iwm_tx(struct iwm_softc *sc, struct mbuf *m, struct ieee80211_node *ni, int ac) m, BPF_DIRECTION_OUT); } #endif + totlen = m->m_pkthdr.len; if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) { - k = ieee80211_get_txkey(ic, wh, ni); - if ((m = ieee80211_encrypt(ic, m, k)) == NULL) - return ENOBUFS; - /* 802.11 header may have moved. */ - wh = mtod(m, struct ieee80211_frame *); + k = ieee80211_get_txkey(ic, wh, ni); + if ((k->k_flags & IEEE80211_KEY_GROUP) || + (k->k_cipher != IEEE80211_CIPHER_CCMP)) { + if ((m = ieee80211_encrypt(ic, m, k)) == NULL) + return ENOBUFS; + /* 802.11 header may have moved. */ + wh = mtod(m, struct ieee80211_frame *); + totlen = m->m_pkthdr.len; + k = NULL; /* skip hardware crypto below */ + } else { + /* HW appends CCMP MIC */ + totlen += IEEE80211_CCMP_HDRLEN; + } } - totlen = m->m_pkthdr.len; flags = 0; if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { @@ -4925,14 +5033,32 @@ iwm_tx(struct iwm_softc *sc, struct mbuf *m, struct ieee80211_node *ni, int ac) /* Copy 802.11 header in TX command. */ memcpy(((uint8_t *)tx) + sizeof(*tx), wh, hdrlen); + if (k != NULL && k->k_cipher == IEEE80211_CIPHER_CCMP) { + /* Trim 802.11 header and prepend CCMP IV. */ + m_adj(m, hdrlen - IEEE80211_CCMP_HDRLEN); + ivp = mtod(m, u_int8_t *); + k->k_tsc++; /* increment the 48-bit PN */ + ivp[0] = k->k_tsc; /* PN0 */ + ivp[1] = k->k_tsc >> 8; /* PN1 */ + ivp[2] = 0; /* Rsvd */ + ivp[3] = k->k_id << 6 | IEEE80211_WEP_EXTIV; + ivp[4] = k->k_tsc >> 16; /* PN2 */ + ivp[5] = k->k_tsc >> 24; /* PN3 */ + ivp[6] = k->k_tsc >> 32; /* PN4 */ + ivp[7] = k->k_tsc >> 40; /* PN5 */ + + tx->sec_ctl = IWM_TX_CMD_SEC_CCM; + memcpy(tx->key, k->k_key, MIN(sizeof(tx->key), k->k_len)); + } else { + /* Trim 802.11 header. */ + m_adj(m, hdrlen); + tx->sec_ctl = 0; + } + flags |= IWM_TX_CMD_FLG_BT_DIS | IWM_TX_CMD_FLG_SEQ_CTL; - tx->sec_ctl = 0; tx->tx_flags |= htole32(flags); - /* Trim 802.11 header. */ - m_adj(m, hdrlen); - err = bus_dmamap_load_mbuf(sc->sc_dmat, data->map, m, BUS_DMA_NOWAIT | BUS_DMA_WRITE); if (err && err != EFBIG) { @@ -6784,6 +6910,115 @@ iwm_node_alloc(struct ieee80211com *ic) return malloc(sizeof (struct iwm_node), M_DEVBUF, M_NOWAIT | M_ZERO); } +int +iwm_set_key_v1(struct ieee80211com *ic, struct ieee80211_node *ni, + struct ieee80211_key *k) +{ + struct iwm_softc *sc = ic->ic_softc; + struct iwm_add_sta_key_cmd_v1 cmd; + + memset(&cmd, 0, sizeof(cmd)); + + cmd.common.key_flags = htole16(IWM_STA_KEY_FLG_CCM | + IWM_STA_KEY_FLG_WEP_KEY_MAP | + ((k->k_id << IWM_STA_KEY_FLG_KEYID_POS) & + IWM_STA_KEY_FLG_KEYID_MSK)); + if (k->k_flags & IEEE80211_KEY_GROUP) + cmd.common.key_flags |= htole16(IWM_STA_KEY_MULTICAST); + + memcpy(cmd.common.key, k->k_key, MIN(sizeof(cmd.common.key), k->k_len)); + cmd.common.key_offset = 0; + cmd.common.sta_id = IWM_STATION_ID; + + return iwm_send_cmd_pdu(sc, IWM_ADD_STA_KEY, IWM_CMD_ASYNC, + sizeof(cmd), &cmd); +} + +int +iwm_set_key(struct ieee80211com *ic, struct ieee80211_node *ni, + struct ieee80211_key *k) +{ + struct iwm_softc *sc = ic->ic_softc; + struct iwm_add_sta_key_cmd cmd; + + if ((k->k_flags & IEEE80211_KEY_GROUP) || + k->k_cipher != IEEE80211_CIPHER_CCMP) { + /* Fallback to software crypto for other ciphers. */ + return (ieee80211_set_key(ic, ni, k)); + } + + if (!isset(sc->sc_ucode_api, IWM_UCODE_TLV_API_TKIP_MIC_KEYS)) + return iwm_set_key_v1(ic, ni, k); + + memset(&cmd, 0, sizeof(cmd)); + + cmd.common.key_flags = htole16(IWM_STA_KEY_FLG_CCM | + IWM_STA_KEY_FLG_WEP_KEY_MAP | + ((k->k_id << IWM_STA_KEY_FLG_KEYID_POS) & + IWM_STA_KEY_FLG_KEYID_MSK)); + if (k->k_flags & IEEE80211_KEY_GROUP) + cmd.common.key_flags |= htole16(IWM_STA_KEY_MULTICAST); + + memcpy(cmd.common.key, k->k_key, MIN(sizeof(cmd.common.key), k->k_len)); + cmd.common.key_offset = 0; + cmd.common.sta_id = IWM_STATION_ID; + + cmd.transmit_seq_cnt = htole64(k->k_tsc); + + return iwm_send_cmd_pdu(sc, IWM_ADD_STA_KEY, IWM_CMD_ASYNC, + sizeof(cmd), &cmd); +} + +void +iwm_delete_key_v1(struct ieee80211com *ic, struct ieee80211_node *ni, + struct ieee80211_key *k) +{ + struct iwm_softc *sc = ic->ic_softc; + struct iwm_add_sta_key_cmd_v1 cmd; + + memset(&cmd, 0, sizeof(cmd)); + + cmd.common.key_flags = htole16(IWM_STA_KEY_NOT_VALID | + IWM_STA_KEY_FLG_NO_ENC | IWM_STA_KEY_FLG_WEP_KEY_MAP | + ((k->k_id << IWM_STA_KEY_FLG_KEYID_POS) & + IWM_STA_KEY_FLG_KEYID_MSK)); + memcpy(cmd.common.key, k->k_key, MIN(sizeof(cmd.common.key), k->k_len)); + cmd.common.key_offset = 0; + cmd.common.sta_id = IWM_STATION_ID; + + iwm_send_cmd_pdu(sc, IWM_ADD_STA_KEY, IWM_CMD_ASYNC, sizeof(cmd), &cmd); +} + +void +iwm_delete_key(struct ieee80211com *ic, struct ieee80211_node *ni, + struct ieee80211_key *k) +{ + struct iwm_softc *sc = ic->ic_softc; + struct iwm_add_sta_key_cmd cmd; + + if ((k->k_flags & IEEE80211_KEY_GROUP) || + (k->k_cipher != IEEE80211_CIPHER_CCMP)) { + /* Fallback to software crypto for other ciphers. */ + ieee80211_delete_key(ic, ni, k); + return; + } + + if (!isset(sc->sc_ucode_api, IWM_UCODE_TLV_API_TKIP_MIC_KEYS)) + return iwm_delete_key_v1(ic, ni, k); + + memset(&cmd, 0, sizeof(cmd)); + + cmd.common.key_flags = htole16(IWM_STA_KEY_NOT_VALID | + IWM_STA_KEY_FLG_NO_ENC | IWM_STA_KEY_FLG_WEP_KEY_MAP | + ((k->k_id << IWM_STA_KEY_FLG_KEYID_POS) & + IWM_STA_KEY_FLG_KEYID_MSK)); + memcpy(cmd.common.key, k->k_key, MIN(sizeof(cmd.common.key), k->k_len)); + cmd.common.key_offset = 0; + cmd.common.sta_id = IWM_STATION_ID; + + iwm_send_cmd_pdu(sc, IWM_ADD_STA_KEY, IWM_CMD_ASYNC, sizeof(cmd), &cmd); +} + void iwm_calib_timeout(void *arg) { @@ -8433,6 +8668,7 @@ iwm_rx_pkt(struct iwm_softc *sc, struct iwm_rx_data *data, struct mbuf_list *ml) IWM_DTS_MEASUREMENT_NOTIF_WIDE): break; + case IWM_ADD_STA_KEY: case IWM_PHY_CONFIGURATION_CMD: case IWM_TX_ANT_CONFIGURATION_CMD: case IWM_ADD_STA: @@ -9283,6 +9519,8 @@ iwm_attach(struct device *parent, struct device *self, void *aux) ic->ic_node_alloc = iwm_node_alloc; ic->ic_bgscan_start = iwm_bgscan; + ic->ic_set_key = iwm_set_key; + ic->ic_delete_key = iwm_delete_key; /* Override 802.11 state transition machine. */ sc->sc_newstate = ic->ic_newstate; diff --git a/sys/dev/pci/if_iwmreg.h b/sys/dev/pci/if_iwmreg.h index 63dac3fc5cc..201ce69014b 100644 --- a/sys/dev/pci/if_iwmreg.h +++ b/sys/dev/pci/if_iwmreg.h @@ -1,4 +1,4 @@ -/* $OpenBSD: if_iwmreg.h,v 1.47 2020/05/15 13:05:04 stsp Exp $ */ +/* $OpenBSD: if_iwmreg.h,v 1.48 2020/05/18 17:56:41 stsp Exp $ */ /****************************************************************************** * @@ -5869,7 +5869,7 @@ struct iwm_umac_scan_iter_complete_notif { * @IWM_STA_KEY_FLG_KEYID_MSK: the index of the key * @IWM_STA_KEY_NOT_VALID: key is invalid * @IWM_STA_KEY_FLG_WEP_13BYTES: set for 13 bytes WEP key - * @IWM_STA_KEY_MULTICAST: set for multical key + * @IWM_STA_KEY_MULTICAST: set for multicast key * @IWM_STA_KEY_MFP: key is used for Management Frame Protection */ #define IWM_STA_KEY_FLG_NO_ENC (0 << 0) @@ -6122,28 +6122,49 @@ struct iwm_add_sta_cmd { #define IWM_STA_AUX_ACTIVITY 4 /** - * struct iwm_add_sta_key_cmd - add/modify sta key - * ( IWM_REPLY_ADD_STA_KEY = 0x17 ) + * struct iwm_add_sta_key_common - add/modify sta key common part + * ( REPLY_ADD_STA_KEY = 0x17 ) * @sta_id: index of station in uCode's station table * @key_offset: key offset in key storage - * @key_flags: type %iwm_sta_key_flag + * @key_flags: IWM_STA_KEY_FLG_* * @key: key material data - * @key2: key material data * @rx_secur_seq_cnt: RX security sequence counter for the key - * @tkip_rx_tsc_byte2: TSC[2] for key mix ph1 detection - * @tkip_rx_ttak: 10-byte unicast TKIP TTAK for Rx */ -struct iwm_add_sta_key_cmd { +struct iwm_add_sta_key_common { uint8_t sta_id; uint8_t key_offset; uint16_t key_flags; - uint8_t key[16]; - uint8_t key2[16]; + uint8_t key[32]; uint8_t rx_secur_seq_cnt[16]; +} __packed; + +/** + * struct iwm_add_sta_key_cmd_v1 - add/modify sta key + * @common: see &struct iwm_add_sta_key_common + * @tkip_rx_tsc_byte2: TSC[2] for key mix ph1 detection + * @reserved: reserved + * @tkip_rx_ttak: 10-byte unicast TKIP TTAK for Rx + */ +struct iwm_add_sta_key_cmd_v1 { + struct iwm_add_sta_key_common common; uint8_t tkip_rx_tsc_byte2; uint8_t reserved; uint16_t tkip_rx_ttak[5]; -} __packed; /* IWM_ADD_MODIFY_STA_KEY_API_S_VER_1 */ +} __packed; /* ADD_MODIFY_STA_KEY_API_S_VER_1 */ + +/** + * struct iwm_add_sta_key_cmd - add/modify sta key + * @common: see &struct iwm_add_sta_key_common + * @rx_mic_key: TKIP RX unicast or multicast key + * @tx_mic_key: TKIP TX key + * @transmit_seq_cnt: TSC, transmit packet number + */ +struct iwm_add_sta_key_cmd { + struct iwm_add_sta_key_common common; + uint64_t rx_mic_key; + uint64_t tx_mic_key; + uint64_t transmit_seq_cnt; +} __packed; /* ADD_MODIFY_STA_KEY_API_S_VER_2 */ /** * status in the response to ADD_STA command @@ -6489,6 +6510,7 @@ struct iwm_rx_packet { #define IWM_FH_RSCSR_FRAME_INVALID 0x55550000 #define IWM_FH_RSCSR_FRAME_ALIGN 0x40 #define IWM_FH_RSCSR_RPA_EN (1 << 25) +#define IWM_FH_RSCSR_RADA_EN (1 << 26) #define IWM_FH_RSCSR_RXQ_POS 16 #define IWM_FH_RSCSR_RXQ_MASK 0x3F0000 |