This makes athn(4) offload CCMP encryption and decryption to hardware.
CCMP is used with WPA2, so this reduces CPU load on WPA2 networks only.

The WPA1 (TKIP) and WEP ciphers remain in software because this simplifies
the driver. TKIP in particular is a annoying to deal with on this hardware.
Old ciphers should not be used anymore anyway and they don't work in 11n mode.

I have successfully tested the following on this device:
athn0 at pci1 dev 0 function 0 "Atheros AR9281" rev 0x01: apic 2 int 16
athn0: AR9280 rev 2 (2T2R), ROM rev 22, address xx:xx:xx:xx:xx:xx

Hostap mode with multiple clients in parallel: wpa2, wpa1, wep, plaintext
Client mode: wpa2, plaintext

Could anyone else test this?

diff c50b0f0215d7dd5773e0ee8623d8aa2c7d2107c6 
72190e7ad96414fa4e11cb3c5ff1dc74b1d69795
blob - 9dd0499c9704c87fbe039b65f02e907284fea120
blob + 8ebdc15cbec490ea4a2f28a0fadab014e3d62645
--- sys/dev/ic/ar5008.c
+++ sys/dev/ic/ar5008.c
@@ -254,6 +254,8 @@ ar5008_attach(struct athn_softc *sc)
        kc_entries_log = MS(base->deviceCap, AR_EEP_DEVCAP_KC_ENTRIES);
        sc->kc_entries = (kc_entries_log != 0) ?
            1 << kc_entries_log : AR_KEYTABLE_SIZE;
+       if (sc->kc_entries > AR_KEYTABLE_SIZE)
+               sc->kc_entries = AR_KEYTABLE_SIZE;
 
        sc->txchainmask = base->txMask;
        if (sc->mac_ver == AR_SREV_VERSION_5416_PCI &&
@@ -781,6 +783,111 @@ ar5008_rx_radiotap(struct athn_softc *sc, struct mbuf 
 }
 #endif
 
+int
+ar5008_ccmp_decap(struct athn_softc *sc, struct mbuf *m, struct ieee80211_node 
*ni)
+{
+       struct ieee80211com *ic = &sc->sc_ic;
+       struct ieee80211_key *k;
+       struct ieee80211_frame *wh;
+       struct ieee80211_rx_ba *ba;
+       uint64_t pn, *prsc;
+       u_int8_t *ivp, *mmie;
+       uint8_t tid;
+       uint16_t kid;
+       int hdrlen, hasqos;
+       uintptr_t entry;
+
+       wh = mtod(m, struct ieee80211_frame *);
+       hdrlen = ieee80211_get_hdrlen(wh);
+       ivp = mtod(m, u_int8_t *) + hdrlen;
+
+       /* find key for decryption */
+       if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) {
+               k = &ni->ni_pairwise_key;
+       } else if ((wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) !=
+           IEEE80211_FC0_TYPE_MGT) {
+               /* retrieve group data key id from IV field */
+               /* check that IV field is present */
+               if (m->m_len < hdrlen + 4)
+                       return 1;
+               kid = ivp[3] >> 6;
+               k = &ic->ic_nw_keys[kid];
+       } else {
+               /* retrieve integrity group key id from MMIE */
+               if (m->m_len < sizeof(*wh) + IEEE80211_MMIE_LEN) {
+                       return 1;
+               }
+               /* it is assumed management frames are contiguous */
+               mmie = (u_int8_t *)wh + m->m_len - IEEE80211_MMIE_LEN;
+               /* check that MMIE is valid */
+               if (mmie[0] != IEEE80211_ELEMID_MMIE || mmie[1] != 16) {
+                       return 1;
+               }
+               kid = LE_READ_2(&mmie[2]);
+               if (kid != 4 && kid != 5) {
+                       return 1;
+               }
+               k = &ic->ic_nw_keys[kid];
+       }
+
+       if (k->k_cipher != IEEE80211_CIPHER_CCMP)
+               return 1;
+
+       /* Sanity checks to ensure this is really a key we installed. */
+       entry = (uintptr_t)k->k_priv;
+       if (k->k_flags & IEEE80211_KEY_GROUP) {
+               if (k->k_id > IEEE80211_WEP_NKID ||
+                   entry != k->k_id)
+                       return 1;
+       } else if (entry != IEEE80211_WEP_NKID +
+           IEEE80211_AID(ni->ni_associd))
+               return 1;
+
+       /* 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;
+       ba = hasqos ? &ni->ni_rx_ba[tid] : NULL;
+       prsc = &k->k_rsc[0];
+
+       /* 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) {
+               if (hasqos && ba->ba_state == IEEE80211_BA_AGREED) {
+                       /*
+                        * This is an A-MPDU subframe.
+                        * Such frames may be received out of order due to
+                        * legitimate retransmissions of failed subframes
+                        * in previous A-MPDUs. Duplicates will be handled
+                        * in ieee80211_inputm() as part of A-MPDU reordering.
+                        *
+                        * XXX TODO We can probably do better than this! Store
+                        * re-ordered PN in BA agreement state and check it?
+                        */
+               } else {
+                       ic->ic_stats.is_ccmp_replays++;
+                       return 1;
+               }
+       }
+       /* Update last seen packet number. */
+       *prsc = pn;
+
+       /* Clear Protected bit and strip IV. */
+       wh->i_fc[1] &= ~IEEE80211_FC1_PROTECTED;
+       memmove(mtod(m, caddr_t) + IEEE80211_CCMP_HDRLEN, wh, hdrlen);
+       m_adj(m, IEEE80211_CCMP_HDRLEN);
+       /* Strip MIC. */
+       m_adj(m, -IEEE80211_CCMP_MICLEN);
+       return 0;
+}
+
 static __inline int
 ar5008_rx_process(struct athn_softc *sc, struct mbuf_list *ml)
 {
@@ -837,9 +944,12 @@ ar5008_rx_process(struct athn_softc *sc, struct mbuf_l
                else if (ds->ds_status8 & AR_RXS8_PHY_ERR)
                        DPRINTFN(6, ("PHY error=0x%x\n",
                            MS(ds->ds_status8, AR_RXS8_PHY_ERR_CODE)));
-               else if (ds->ds_status8 & AR_RXS8_DECRYPT_CRC_ERR)
+               else if ((ds->ds_status8 & AR_RXS8_DECRYPT_CRC_ERR) ||
+                   (!IEEE80211_IS_MULTICAST(wh->i_addr1) &&
+                   (ds->ds_status8 & AR_RXS8_KEY_MISS))) {
                        DPRINTFN(6, ("Decryption CRC error\n"));
-               else if (ds->ds_status8 & AR_RXS8_MICHAEL_ERR) {
+                       ic->ic_stats.is_ccmp_dec_errs++;
+               } else if (ds->ds_status8 & AR_RXS8_MICHAEL_ERR) {
                        DPRINTFN(2, ("Michael MIC failure\n"));
                        /* Report Michael MIC failures to net80211. */
                        ic->ic_stats.is_rx_locmicfail++;
@@ -848,7 +958,8 @@ ar5008_rx_process(struct athn_softc *sc, struct mbuf_l
                         * XXX Check that it is not a control frame
                         * (invalid MIC failures on valid ctl frames).
                         */
-               }
+               } else if (ds->ds_status8 & AR_RXS8_DECRYPT_BUSY_ERR)
+                       ic->ic_stats.is_ccmp_dec_errs++;
                ifp->if_ierrors++;
                goto skip;
        }
@@ -911,6 +1022,7 @@ ar5008_rx_process(struct athn_softc *sc, struct mbuf_l
                        memmove((caddr_t)wh + 2, wh, hdrlen);
                        m_adj(m, 2);
                }
+               wh = mtod(m, struct ieee80211_frame *);
        }
 #if NBPFILTER > 0
        if (__predict_false(sc->sc_drvbpf != NULL))
@@ -924,6 +1036,20 @@ ar5008_rx_process(struct athn_softc *sc, struct mbuf_l
        rxi.rxi_rssi = MS(ds->ds_status4, AR_RXS4_RSSI_COMBINED);
        rxi.rxi_rssi += AR_DEFAULT_NOISE_FLOOR;
        rxi.rxi_tstamp = ds->ds_status2;
+       if ((wh->i_fc[1] & IEEE80211_FC1_PROTECTED) &&
+           (ic->ic_flags & IEEE80211_F_RSNON) &&
+           (ni->ni_flags & IEEE80211_NODE_RXPROT) &&
+           (ni->ni_rsncipher == IEEE80211_CIPHER_CCMP ||
+           (IEEE80211_IS_MULTICAST(wh->i_addr1) &&
+           ic->ic_rsngroupcipher == IEEE80211_CIPHER_CCMP))) {
+               if (ar5008_ccmp_decap(sc, m, ni) != 0) {
+                       ifp->if_ierrors++;
+                       ieee80211_release_node(ic, ni);
+                       m_freem(m);
+                       goto skip;
+               }
+               rxi.rxi_flags |= IEEE80211_RXI_HWDEC;
+       }
        ieee80211_inputm(ifp, m, ni, &rxi, ml);
 
        /* Node is no longer needed. */
@@ -1330,9 +1456,34 @@ ar5008_tx(struct athn_softc *sc, struct mbuf *m, struc
 
        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);
-               wh = mtod(m, struct ieee80211_frame *);
+               if (k->k_cipher == IEEE80211_CIPHER_CCMP) {
+                       struct mbuf *n;
+                       u_int hdrlen = ieee80211_get_hdrlen(wh);
+                       uint8_t *ivp;
+                       int off;
+
+                       /* Insert IV for CCMP hardware encryption. */
+                       n = m_makespace(m, hdrlen, IEEE80211_CCMP_HDRLEN, &off);
+                       if (n == NULL) {
+                               m_freem(m);
+                               return (ENOBUFS);
+                       }
+                       ivp = mtod(n, uint8_t *) + off;
+                       k->k_tsc++;
+                       ivp[0] = k->k_tsc;
+                       ivp[1] = k->k_tsc >> 8;
+                       ivp[2] = 0;
+                       ivp[3] = k->k_id << 6 | IEEE80211_WEP_EXTIV;
+                       ivp[4] = k->k_tsc >> 16;
+                       ivp[5] = k->k_tsc >> 24;
+                       ivp[6] = k->k_tsc >> 32;
+                       ivp[7] = k->k_tsc >> 40;
+               } else {
+                       if ((m = ieee80211_encrypt(ic, m, k)) == NULL)
+                               return (ENOBUFS);
+                       wh = mtod(m, struct ieee80211_frame *);
+                       k = NULL; /* skip hardware crypto further below */
+               }
        }
 
        /* XXX 2-byte padding for QoS and 4-addr headers. */
@@ -1461,28 +1612,13 @@ ar5008_tx(struct athn_softc *sc, struct mbuf *m, struc
             IEEE80211_QOS_ACK_POLICY_NOACK))
                ds->ds_ctl1 |= AR_TXC1_NO_ACK;
 
-       if (0 && k != NULL) {
-               /*
-                * Map 802.11 cipher to hardware encryption type and
-                * compute MIC+ICV overhead.
-                */
-               switch (k->k_cipher) {
-               case IEEE80211_CIPHER_WEP40:
-               case IEEE80211_CIPHER_WEP104:
-                       encrtype = AR_ENCR_TYPE_WEP;
-                       totlen += 4;
-                       break;
-               case IEEE80211_CIPHER_TKIP:
-                       encrtype = AR_ENCR_TYPE_TKIP;
-                       totlen += 12;
-                       break;
-               case IEEE80211_CIPHER_CCMP:
+       if (k != NULL) {
+               /* Map 802.11 cipher to hardware encryption type. */
+               if (k->k_cipher == IEEE80211_CIPHER_CCMP) {
                        encrtype = AR_ENCR_TYPE_AES;
-                       totlen += 8;
-                       break;
-               default:
+                       totlen += IEEE80211_CCMP_MICLEN;
+               } else
                        panic("unsupported cipher");
-               }
                /*
                 * NB: The key cache entry index is stored in the key
                 * private field when the key is installed.
blob - a45fe825940b6bb6810a7de7830ccd87d6bcb6fc
blob + eed13c59a4d1991bfa8a2827f3d190b6ae5e6a42
--- sys/dev/ic/ar5008reg.h
+++ sys/dev/ic/ar5008reg.h
@@ -927,6 +927,7 @@ struct ar_rx_desc {
 #define AR_RXS8_KEY_IDX_S              9
 #define AR_RXS8_POST_DELIM_CRC_ERR     0x00040000
 #define AR_RXS8_DECRYPT_BUSY_ERR       0x40000000
+#define AR_RXS8_KEY_MISS               0x80000000
 
 #define AR_MAX_PWR_RANGE_IN_HALF_DB    64
 #define AR9285_PD_GAIN_BOUNDARY_DEFAULT        58
blob - b3532eb8263f678c80a44257fe4f55820458a18c
blob + dfc5b0238cbda7022d981b6e29c6cd289662e4af
--- sys/dev/ic/athn.c
+++ sys/dev/ic/athn.c
@@ -244,9 +244,10 @@ athn_attach(struct athn_softc *sc)
        /*
         * In HostAP mode, the number of STAs that we can handle is
         * limited by the number of entries in the HW key cache.
-        * TKIP keys consume 2 entries in the cache.
+        * TKIP keys would consume 2 entries in this cache but we
+        * only use the hardware crypto engine for CCMP.
         */
-       ic->ic_max_nnodes = (sc->kc_entries / 2) - IEEE80211_WEP_NKID;
+       ic->ic_max_nnodes = sc->kc_entries - IEEE80211_WEP_NKID;
        if (ic->ic_max_nnodes > IEEE80211_CACHE_SIZE)
                ic->ic_max_nnodes = IEEE80211_CACHE_SIZE;
 
@@ -373,10 +374,8 @@ athn_attach(struct athn_softc *sc)
        ic->ic_newassoc = athn_newassoc;
        ic->ic_updateslot = athn_updateslot;
        ic->ic_updateedca = athn_updateedca;
-#ifdef notyet
        ic->ic_set_key = athn_set_key;
        ic->ic_delete_key = athn_delete_key;
-#endif
 
        /* Override 802.11 state transition machine. */
        sc->sc_newstate = ic->ic_newstate;
@@ -990,6 +989,12 @@ athn_reset_key(struct athn_softc *sc, int entry)
         * NB: Key cache registers access special memory area that requires
         * two 32-bit writes to actually update the values in the internal
         * memory.  Consequently, writes must be grouped by pair.
+        *
+        * All writes to registers with an offset of 0x0 or 0x8 write to a
+        * temporary register. A write to a register with an offset of 0x4
+        * or 0xc writes concatenates the written value with the value in
+        * the temporary register and writes the result to key cache memory.
+        * The actual written memory area is 50 bits wide.
         */
        AR_WRITE(sc, AR_KEYTABLE_KEY0(entry), 0);
        AR_WRITE(sc, AR_KEYTABLE_KEY1(entry), 0);
@@ -1011,58 +1016,29 @@ athn_set_key(struct ieee80211com *ic, struct ieee80211
     struct ieee80211_key *k)
 {
        struct athn_softc *sc = ic->ic_softc;
-       const uint8_t *txmic, *rxmic, *key, *addr;
-       uintptr_t entry, micentry;
-       uint32_t type, lo, hi;
+       const uint8_t *key, *addr;
+       uintptr_t entry;
+       uint32_t lo, hi, unicast;
 
-       switch (k->k_cipher) {
-       case IEEE80211_CIPHER_WEP40:
-               type = AR_KEYTABLE_TYPE_40;
-               break;
-       case IEEE80211_CIPHER_WEP104:
-               type = AR_KEYTABLE_TYPE_104;
-               break;
-       case IEEE80211_CIPHER_TKIP:
-               type = AR_KEYTABLE_TYPE_TKIP;
-               break;
-       case IEEE80211_CIPHER_CCMP:
-               type = AR_KEYTABLE_TYPE_CCM;
-               break;
-       default:
-               /* Fallback to software crypto for other ciphers. */
-               return (ieee80211_set_key(ic, ni, k));
+       if (k->k_cipher != IEEE80211_CIPHER_CCMP) {
+               /* Use software crypto for ciphers other than CCMP. */
+               return ieee80211_set_key(ic, ni, k);
        }
 
-       if (!(k->k_flags & IEEE80211_KEY_GROUP))
+       if (!(k->k_flags & IEEE80211_KEY_GROUP)) {
                entry = IEEE80211_WEP_NKID + IEEE80211_AID(ni->ni_associd);
-       else
+               if (entry >= sc->kc_entries - IEEE80211_WEP_NKID)
+                       return ENOSPC;
+       } else {
                entry = k->k_id;
+               if (entry > IEEE80211_WEP_NKID)
+                       return ENOSPC;
+       }
        k->k_priv = (void *)entry;
 
        /* NB: See note about key cache registers access above. */
        key = k->k_key;
-       if (type == AR_KEYTABLE_TYPE_TKIP) {
-#ifndef IEEE80211_STA_ONLY
-               if (ic->ic_opmode == IEEE80211_M_HOSTAP) {
-                       txmic = &key[16];
-                       rxmic = &key[24];
-               } else
-#endif
-               {
-                       rxmic = &key[16];
-                       txmic = &key[24];
-               }
-               /* Tx+Rx MIC key is at entry + 64. */
-               micentry = entry + 64;
-               AR_WRITE(sc, AR_KEYTABLE_KEY0(micentry), LE_READ_4(&rxmic[0]));
-               AR_WRITE(sc, AR_KEYTABLE_KEY1(micentry), LE_READ_2(&txmic[2]));
 
-               AR_WRITE(sc, AR_KEYTABLE_KEY2(micentry), LE_READ_4(&rxmic[4]));
-               AR_WRITE(sc, AR_KEYTABLE_KEY3(micentry), LE_READ_2(&txmic[0]));
-
-               AR_WRITE(sc, AR_KEYTABLE_KEY4(micentry), LE_READ_4(&txmic[4]));
-               AR_WRITE(sc, AR_KEYTABLE_TYPE(micentry), AR_KEYTABLE_TYPE_CLR);
-       }
        AR_WRITE(sc, AR_KEYTABLE_KEY0(entry), LE_READ_4(&key[ 0]));
        AR_WRITE(sc, AR_KEYTABLE_KEY1(entry), LE_READ_2(&key[ 4]));
 
@@ -1070,19 +1046,45 @@ athn_set_key(struct ieee80211com *ic, struct ieee80211
        AR_WRITE(sc, AR_KEYTABLE_KEY3(entry), LE_READ_2(&key[10]));
 
        AR_WRITE(sc, AR_KEYTABLE_KEY4(entry), LE_READ_4(&key[12]));
-       AR_WRITE(sc, AR_KEYTABLE_TYPE(entry), type);
+       AR_WRITE(sc, AR_KEYTABLE_TYPE(entry), AR_KEYTABLE_TYPE_CCM);
 
+       unicast = AR_KEYTABLE_VALID;
        if (!(k->k_flags & IEEE80211_KEY_GROUP)) {
                addr = ni->ni_macaddr;
                lo = LE_READ_4(&addr[0]);
                hi = LE_READ_2(&addr[4]);
                lo = lo >> 1 | hi << 31;
                hi = hi >> 1;
-       } else
-               lo = hi = 0;
+       } else {
+#ifndef IEEE80211_STA_ONLY
+               if (ic->ic_opmode == IEEE80211_M_HOSTAP) {
+                       uint8_t groupaddr[ETHER_ADDR_LEN];
+                       IEEE80211_ADDR_COPY(groupaddr, ic->ic_myaddr);
+                       groupaddr[0] |= 0x01;
+                       lo = LE_READ_4(&groupaddr[0]);
+                       hi = LE_READ_2(&groupaddr[4]);
+                       lo = lo >> 1 | hi << 31;
+                       hi = hi >> 1;
+                       /*
+                        * KEYTABLE_VALID indicates that the address
+                        * is a unicast address which must match the
+                        * transmitter address when decrypting frames.
+                        * Not setting KEYTABLE_VALID allows hardware to
+                        * use this key for multicast frame decryption.
+                        */
+                       unicast = 0;
+               } else
+#endif
+                       lo = hi = 0;
+       }
        AR_WRITE(sc, AR_KEYTABLE_MAC0(entry), lo);
-       AR_WRITE(sc, AR_KEYTABLE_MAC1(entry), hi | AR_KEYTABLE_VALID);
+       AR_WRITE(sc, AR_KEYTABLE_MAC1(entry), hi | unicast);
+
+       /* Enable HW crypto. */
+       AR_CLRBITS(sc, AR_DIAG_SW, AR_DIAG_ENCRYPT_DIS | AR_DIAG_DECRYPT_DIS);
+
        AR_WRITE_BARRIER(sc);
+       DPRINTF(("configured HW crypto key entry %lu for STA %s\n", entry, 
ether_sprintf(ni->ni_macaddr)));
        return (0);
 }
 
@@ -1093,22 +1095,12 @@ athn_delete_key(struct ieee80211com *ic, struct ieee80
        struct athn_softc *sc = ic->ic_softc;
        uintptr_t entry;
 
-       switch (k->k_cipher) {
-       case IEEE80211_CIPHER_WEP40:
-       case IEEE80211_CIPHER_WEP104:
-       case IEEE80211_CIPHER_CCMP:
+       if (k->k_cipher == IEEE80211_CIPHER_CCMP) {
                entry = (uintptr_t)k->k_priv;
                athn_reset_key(sc, entry);
-               break;
-       case IEEE80211_CIPHER_TKIP:
-               entry = (uintptr_t)k->k_priv;
-               athn_reset_key(sc, entry);
-               athn_reset_key(sc, entry + 64);
-               break;
-       default:
-               /* Fallback to software crypto for other ciphers. */
+               explicit_bzero(k, sizeof(*k));
+       } else
                ieee80211_delete_key(ic, ni, k);
-       }
 }
 
 void

Reply via email to