Implement support for multiple packets per receive DMA buffer in iwm(4).
authorstsp <stsp@openbsd.org>
Mon, 24 Apr 2017 09:31:31 +0000 (09:31 +0000)
committerstsp <stsp@openbsd.org>
Mon, 24 Apr 2017 09:31:31 +0000 (09:31 +0000)
We can now clear SINGLE_FRAME_MSK in the receive configuration register.
Linux has not set this bit in years, and Dragonfly stopped setting it
in commit b5eb43f0280bbcfd26af51cf5a4b8e8ff3590b67.
This is a prerequisite for monitor mode since the firmware seems to ignore
the SINGLE_FRAME_MSK bit while running in monitor mode and sends us garbage.

Tested on 7260, 7265, and 8260 devices.

sys/dev/pci/if_iwm.c
sys/dev/pci/if_iwmreg.h

index 4970ce7..f5deacb 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: if_iwm.c,v 1.170 2017/04/21 17:54:02 stsp Exp $       */
+/*     $OpenBSD: if_iwm.c,v 1.171 2017/04/24 09:31:31 stsp Exp $       */
 
 /*
  * Copyright (c) 2014, 2016 genua gmbh <info@genua.de>
@@ -362,8 +362,8 @@ int iwm_get_signal_strength(struct iwm_softc *, struct iwm_rx_phy_info *);
 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_rx_mpdu(struct iwm_softc *, struct iwm_rx_packet *,
-           struct iwm_rx_data *);
+void   iwm_rx_mpdu(struct iwm_softc *, struct mbuf *);
+void   iwm_rx_pkt(struct iwm_softc *, struct iwm_rx_data *);
 void   iwm_rx_tx_cmd_single(struct iwm_softc *, struct iwm_rx_packet *,
            struct iwm_node *);
 void   iwm_rx_tx_cmd(struct iwm_softc *, struct iwm_rx_packet *,
@@ -449,6 +449,7 @@ const char *iwm_desc_lookup(uint32_t);
 void   iwm_nic_error(struct iwm_softc *);
 void   iwm_nic_umac_error(struct iwm_softc *);
 #endif
+int    iwm_rx_frame(struct iwm_softc *, struct mbuf *);
 void   iwm_notif_intr(struct iwm_softc *);
 int    iwm_intr(void *);
 int    iwm_match(struct device *, void *, void *);
@@ -1672,7 +1673,6 @@ iwm_nic_rx_init(struct iwm_softc *sc)
            IWM_FH_RCSR_RX_CONFIG_CHNL_EN_ENABLE_VAL            |
            IWM_FH_RCSR_CHNL0_RX_IGNORE_RXF_EMPTY               |  /* HW bug */
            IWM_FH_RCSR_CHNL0_RX_CONFIG_IRQ_DEST_INT_HOST_VAL   |
-           IWM_FH_RCSR_CHNL0_RX_CONFIG_SINGLE_FRAME_MSK        |
            (IWM_RX_RB_TIMEOUT << IWM_FH_RCSR_RX_CONFIG_REG_IRQ_RBTH_POS) |
            IWM_FH_RCSR_RX_CONFIG_REG_VAL_RB_SIZE_4K            |
            IWM_RX_QUEUE_SIZE_LOG << IWM_FH_RCSR_RX_CONFIG_RBDCB_SIZE_POS);
@@ -3296,43 +3296,23 @@ iwm_get_noise(const struct iwm_statistics_rx_non_phy *stats)
        return (nbant == 0) ? -127 : (total / nbant) - 107;
 }
 
-void
-iwm_rx_rx_mpdu(struct iwm_softc *sc, struct iwm_rx_packet *pkt,
-    struct iwm_rx_data *data)
+int
+iwm_rx_frame(struct iwm_softc *sc, struct mbuf *m)
 {
        struct ieee80211com *ic = &sc->sc_ic;
        struct ieee80211_frame *wh;
        struct ieee80211_node *ni;
        struct ieee80211_channel *c = NULL;
        struct ieee80211_rxinfo rxi;
-       struct mbuf *m;
        struct iwm_rx_phy_info *phy_info;
-       struct iwm_rx_mpdu_res_start *rx_res;
        int device_timestamp;
-       uint32_t len;
-       uint32_t rx_pkt_status;
        int rssi;
 
-       bus_dmamap_sync(sc->sc_dmat, data->map, 0, IWM_RBUF_SIZE,
-           BUS_DMASYNC_POSTREAD);
-
        phy_info = &sc->sc_last_phy_info;
-       rx_res = (struct iwm_rx_mpdu_res_start *)pkt->data;
-       wh = (struct ieee80211_frame *)(pkt->data + sizeof(*rx_res));
-       len = le16toh(rx_res->byte_count);
-       rx_pkt_status = le32toh(*(uint32_t *)(pkt->data +
-           sizeof(*rx_res) + len));
-
-       m = data->m;
-       m->m_data = pkt->data + sizeof(*rx_res);
-       m->m_pkthdr.len = m->m_len = len;
+       wh = mtod(m, struct ieee80211_frame *);
 
        if (__predict_false(phy_info->cfg_phy_cnt > 20))
-               return;
-
-       if (!(rx_pkt_status & IWM_RX_MPDU_RES_STATUS_CRC_OK) ||
-           !(rx_pkt_status & IWM_RX_MPDU_RES_STATUS_OVERRUN_OK))
-               return; /* drop */
+               return EINVAL;
 
        device_timestamp = le32toh(phy_info->system_timestamp);
 
@@ -3344,9 +3324,6 @@ iwm_rx_rx_mpdu(struct iwm_softc *sc, struct iwm_rx_packet *pkt,
        rssi = (0 - IWM_MIN_DBM) + rssi;        /* normalize */
        rssi = MIN(rssi, ic->ic_max_rssi);      /* clip to max. 100% */
 
-       if (iwm_rx_addbuf(sc, IWM_RBUF_SIZE, sc->rxq.cur) != 0)
-               return;
-
        if (le32toh(phy_info->channel) < nitems(ic->ic_channels))
                c = &ic->ic_channels[le32toh(phy_info->channel)];
 
@@ -3415,6 +3392,8 @@ iwm_rx_rx_mpdu(struct iwm_softc *sc, struct iwm_rx_packet *pkt,
 #endif
        ieee80211_input(IC2IFP(ic), m, ni, &rxi);
        ieee80211_release_node(ic, ni);
+
+       return 0;
 }
 
 void
@@ -6472,47 +6451,93 @@ do {                                                                    \
 #define ADVANCE_RXQ(sc) (sc->rxq.cur = (sc->rxq.cur + 1) % IWM_RX_RING_COUNT);
 
 void
-iwm_notif_intr(struct iwm_softc *sc)
+iwm_rx_mpdu(struct iwm_softc *sc, struct mbuf *m)
 {
-       uint16_t hw;
+       struct ifnet *ifp = IC2IFP(&sc->sc_ic);
+       struct iwm_rx_packet *pkt;
+       struct iwm_rx_mpdu_res_start *rx_res;
+       uint32_t len;
+       uint32_t rx_pkt_status;
+       int rxfail;
 
-       bus_dmamap_sync(sc->sc_dmat, sc->rxq.stat_dma.map,
-           0, sc->rxq.stat_dma.size, BUS_DMASYNC_POSTREAD);
+       pkt = mtod(m, struct iwm_rx_packet *);
+       rx_res = (struct iwm_rx_mpdu_res_start *)pkt->data;
+       len = le16toh(rx_res->byte_count);
+       rx_pkt_status = le32toh(*(uint32_t *)(pkt->data +
+           sizeof(*rx_res) + len));
 
-       hw = le16toh(sc->rxq.stat->closed_rb_num) & 0xfff;
-       while (sc->rxq.cur != hw) {
-               struct iwm_rx_data *data = &sc->rxq.data[sc->rxq.cur];
-               struct iwm_rx_packet *pkt;
-               struct iwm_cmd_response *cresp;
-               int qid, idx, code;
+       rxfail = ((rx_pkt_status & IWM_RX_MPDU_RES_STATUS_CRC_OK) == 0 ||
+           (rx_pkt_status & IWM_RX_MPDU_RES_STATUS_OVERRUN_OK) == 0);
+       if (rxfail) {
+               ifp->if_ierrors++;
+               m_freem(m);
+               return;
+       }
 
-               bus_dmamap_sync(sc->sc_dmat, data->map, 0, sizeof(*pkt),
-                   BUS_DMASYNC_POSTREAD);
-               pkt = mtod(data->m, struct iwm_rx_packet *);
+       /* Extract the 802.11 frame. */
+       m->m_data = (caddr_t)pkt->data + sizeof(*rx_res);
+       m->m_pkthdr.len = m->m_len = len;
+       if (iwm_rx_frame(sc, m) != 0) {
+               ifp->if_ierrors++;
+               m_freem(m);
+       }
+}
 
+void
+iwm_rx_pkt(struct iwm_softc *sc, struct iwm_rx_data *data)
+{
+       struct ifnet *ifp = IC2IFP(&sc->sc_ic);
+       struct iwm_rx_packet *pkt;
+       struct iwm_cmd_response *cresp;
+       uint32_t offset = 0, nmpdu = 0, len;
+       struct mbuf *m0;
+       const uint32_t minsz = sizeof(uint32_t) + sizeof(struct iwm_cmd_header);
+       int qid, idx, code;
+
+       bus_dmamap_sync(sc->sc_dmat, data->map, 0, IWM_RBUF_SIZE,
+           BUS_DMASYNC_POSTREAD);
+
+       m0 = data->m;
+       while (offset + minsz < IWM_RBUF_SIZE) {
+               pkt = (struct iwm_rx_packet *)(m0->m_data + offset);
                qid = pkt->hdr.qid & ~0x80;
                idx = pkt->hdr.idx;
-
                code = IWM_WIDE_ID(pkt->hdr.flags, pkt->hdr.code);
 
-               /*
-                * randomly get these from the firmware, no idea why.
-                * they at least seem harmless, so just ignore them for now
-                */
-               if (__predict_false((pkt->hdr.code == 0 && qid == 0 && idx == 0)
-                   || pkt->len_n_flags == htole32(0x55550000))) {
-                       ADVANCE_RXQ(sc);
-                       continue;
+               if ((code == 0 && qid == 0 && idx == 0) ||
+                   pkt->len_n_flags == htole32(IWM_FH_RSCSR_FRAME_INVALID)) {
+                       break;
+               }
+
+               if (code == IWM_REPLY_RX_MPDU_CMD && ++nmpdu == 1) {
+                       /* Take mbuf m0 off the RX ring. */
+                       if (iwm_rx_addbuf(sc, IWM_RBUF_SIZE, sc->rxq.cur)) {
+                               ifp->if_ierrors++;
+                               break;
+                       }
+                       KASSERT(data->m != m0);
                }
 
+               len = le32toh(pkt->len_n_flags) & IWM_FH_RSCSR_FRAME_SIZE_MSK;
+               len += sizeof(uint32_t); /* account for status word */
+
                switch (code) {
                case IWM_REPLY_RX_PHY_CMD:
                        iwm_rx_rx_phy_cmd(sc, pkt, data);
                        break;
 
-               case IWM_REPLY_RX_MPDU_CMD:
-                       iwm_rx_rx_mpdu(sc, pkt, data);
+               case IWM_REPLY_RX_MPDU_CMD: {
+                       /* Always copy from offset zero to preserve m_pkthdr. */
+                       struct mbuf *m = m_copym(m0, 0, M_COPYALL, M_DONTWAIT);
+                       if (m == NULL) {
+                               ifp->if_ierrors++;
+                               break;
+                       }
+                       m_adj(m, offset);
+                       m_adj(m, -(m->m_pkthdr.len - len));
+                       iwm_rx_mpdu(sc, m);
                        break;
+               }
 
                case IWM_TX_CMD:
                        iwm_rx_tx_cmd(sc, pkt, data);
@@ -6731,6 +6756,26 @@ iwm_notif_intr(struct iwm_softc *sc)
                        iwm_cmd_done(sc, pkt);
                }
 
+               offset += roundup(len, IWM_FH_RSCSR_FRAME_ALIGN);
+       }
+
+       if (m0 != data->m)
+               m_freem(m0);
+}
+
+void
+iwm_notif_intr(struct iwm_softc *sc)
+{
+       uint16_t hw;
+
+       bus_dmamap_sync(sc->sc_dmat, sc->rxq.stat_dma.map,
+           0, sc->rxq.stat_dma.size, BUS_DMASYNC_POSTREAD);
+
+       hw = le16toh(sc->rxq.stat->closed_rb_num) & 0xfff;
+       while (sc->rxq.cur != hw) {
+               struct iwm_rx_data *data = &sc->rxq.data[sc->rxq.cur];
+
+               iwm_rx_pkt(sc, data);
                ADVANCE_RXQ(sc);
        }
 
index b2576e5..288c9c4 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: if_iwmreg.h,v 1.25 2017/04/04 00:40:52 claudio Exp $  */
+/*     $OpenBSD: if_iwmreg.h,v 1.26 2017/04/24 09:31:31 stsp Exp $     */
 
 /******************************************************************************
  *
@@ -5876,7 +5876,13 @@ struct iwm_rx_packet {
         * 31:    flag flush RB request
         * 30:    flag ignore TC (terminal counter) request
         * 29:    flag fast IRQ request
-        * 28-14: Reserved
+        * 28-26: Reserved
+        * 25:    Offload enabled
+        * 24:    RPF enabled
+        * 23:    RSS enabled
+        * 22:    Checksum enabled
+        * 21-16: RX queue
+        * 15-14: Reserved
         * 13-00: RX frame size
         */
        uint32_t len_n_flags;
@@ -5885,6 +5891,11 @@ struct iwm_rx_packet {
 } __packed;
 
 #define        IWM_FH_RSCSR_FRAME_SIZE_MSK     0x00003fff
+#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_RXQ_POS            16
+#define        IWM_FH_RSCSR_RXQ_MASK           0x3F0000
 
 static uint32_t
 iwm_rx_packet_len(const struct iwm_rx_packet *pkt)