The iwm(4) driver pre-allocates fw command payload buffers of 320 bytes.

For some firmware commands, particularly those used when configuring
the PHY (iwm_send_phy_db_cmd) and running scans (iwm_mvm_scan_request),
the payload exceeds 320 bytes. I've seen somewhere between 2k and 3.5k
being used. Precisely these commands fail often while we're trying to
bring the interface up. You've probably seen "could not initiate scan".
The PHY failure case doesn't print anything unless IWM_DEBUG is set.

If the payload doesn't fit, the driver tries to use an mbuf instead
of the pre-allocated payload buffer. This seems to be based on the
approach taken in iwn(4).

The current code seems confused about sizes.
If a command requires 'sizeof(cmd->hdr) + paylen' bytes, the driver only
maps hcmd->len[0] bytes for DMA. But 'paylen' is the sum of hcmod->len[0]
and hcmd->len[1]. And the DMA map used to map large payloads only handles
mappings of up to MCLBYTES, which is only 2k...

Fix this by sizing the mbuf and its DMA map correctly.

Index: if_iwm.c
===================================================================
RCS file: /cvs/src/sys/dev/pci/if_iwm.c,v
retrieving revision 1.55
diff -u -p -r1.55 if_iwm.c
--- if_iwm.c    11 Oct 2015 10:22:28 -0000      1.55
+++ if_iwm.c    11 Oct 2015 14:37:41 -0000
@@ -1108,14 +1108,21 @@ iwm_alloc_tx_ring(struct iwm_softc *sc, 
        paddr = ring->cmd_dma.paddr;
        for (i = 0; i < IWM_TX_RING_COUNT; i++) {
                struct iwm_tx_data *data = &ring->data[i];
+               size_t mapsize;
 
                data->cmd_paddr = paddr;
                data->scratch_paddr = paddr + sizeof(struct iwm_cmd_header)
                    + offsetof(struct iwm_tx_cmd, scratch);
                paddr += sizeof(struct iwm_device_cmd);
 
-               error = bus_dmamap_create(sc->sc_dmat, MCLBYTES,
-                   IWM_NUM_OF_TBS - 2, MCLBYTES, 0, BUS_DMA_NOWAIT,
+               /* FW commands may require more mapped space than packets. */
+               if (qid == IWM_MVM_CMD_QUEUE)
+                       mapsize = (sizeof(struct iwm_cmd_header) +
+                           IWM_MAX_CMD_PAYLOAD_SIZE);
+               else
+                       mapsize = MCLBYTES;
+               error = bus_dmamap_create(sc->sc_dmat, mapsize,
+                   IWM_NUM_OF_TBS - 2, mapsize, 0, BUS_DMA_NOWAIT,
                    &data->map);
                if (error != 0) {
                        printf("%s: could not create TX buf DMA map\n", 
DEVNAME(sc));
@@ -3393,30 +3400,31 @@ iwm_send_cmd(struct iwm_softc *sc, struc
        data = &ring->data[ring->cur];
 
        if (paylen > sizeof(cmd->data)) {
-               /* Command is too large */
-               if (sizeof(cmd->hdr) + paylen > IWM_RBUF_SIZE) {
+               /* Command is too large to fit in pre-allocated space. */
+               size_t totlen = sizeof(cmd->hdr) + paylen;
+               if (paylen > IWM_MAX_CMD_PAYLOAD_SIZE) {
+                       printf("%s: firmware command too long (%zd bytes)\n",
+                           DEVNAME(sc), totlen);
                        error = EINVAL;
                        goto out;
                }
-               m = m_gethdr(M_DONTWAIT, MT_DATA);
+               m = MCLGETI(NULL, M_DONTWAIT, NULL, totlen);
                if (m == NULL) {
-                       error = ENOMEM;
-                       goto out;
-               }
-               MCLGETI(m, M_DONTWAIT, NULL, IWM_RBUF_SIZE);
-               if (!(m->m_flags & M_EXT)) {
-                       m_freem(m);
+                       printf("%s: could not get fw cmd mbuf (%zd bytes)\n",
+                           DEVNAME(sc), totlen);
                        error = ENOMEM;
                        goto out;
                }
                cmd = mtod(m, struct iwm_device_cmd *);
                error = bus_dmamap_load(sc->sc_dmat, data->map, cmd,
-                   hcmd->len[0], NULL, BUS_DMA_NOWAIT | BUS_DMA_WRITE);
+                   totlen, NULL, BUS_DMA_NOWAIT | BUS_DMA_WRITE);
                if (error != 0) {
+                       printf("%s: could not load fw cmd mbuf (%zd bytes)\n",
+                           DEVNAME(sc), totlen);
                        m_freem(m);
                        goto out;
                }
-               data->m = m;
+               data->m = m; /* mbuf will be freed in iwm_cmd_done() */
                paddr = data->map->dm_segs[0].ds_addr;
        } else {
                cmd = &ring->cmd[ring->cur];
@@ -3447,13 +3455,13 @@ iwm_send_cmd(struct iwm_softc *sc, struc
            code, hcmd->len[0] + hcmd->len[1] + sizeof(cmd->hdr),
            async ? " (async)" : ""));
 
-       if (hcmd->len[0] > sizeof(cmd->data)) {
-               bus_dmamap_sync(sc->sc_dmat, data->map, 0, hcmd->len[0],
-                   BUS_DMASYNC_PREWRITE);
+       if (paylen > sizeof(cmd->data)) {
+               bus_dmamap_sync(sc->sc_dmat, data->map, 0,
+                   sizeof(cmd->hdr) + paylen, BUS_DMASYNC_PREWRITE);
        } else {
                bus_dmamap_sync(sc->sc_dmat, ring->cmd_dma.map,
                    (char *)(void *)cmd - (char *)(void *)ring->cmd_dma.vaddr,
-                   hcmd->len[0] + 4, BUS_DMASYNC_PREWRITE);
+                   sizeof(cmd->hdr) + hcmd->len[0], BUS_DMASYNC_PREWRITE);
        }
        bus_dmamap_sync(sc->sc_dmat, ring->desc_dma.map,
            (char *)(void *)desc - (char *)(void *)ring->desc_dma.vaddr,
Index: if_iwmreg.h
===================================================================
RCS file: /cvs/src/sys/dev/pci/if_iwmreg.h,v
retrieving revision 1.4
diff -u -p -r1.4 if_iwmreg.h
--- if_iwmreg.h 15 Jun 2015 08:06:11 -0000      1.4
+++ if_iwmreg.h 11 Oct 2015 14:36:04 -0000
@@ -5141,6 +5141,7 @@ enum iwm_power_scheme {
 };
 
 #define IWM_DEF_CMD_PAYLOAD_SIZE 320
+#define IWM_MAX_CMD_PAYLOAD_SIZE (4096 - sizeof(struct iwm_cmd_header))
 #define IWM_CMD_FAILED_MSK 0x40
 
 struct iwm_device_cmd {

Reply via email to