Any objections to putting this in soon? Any OKs?
I'd like to move forward with iwm(4) (less fixing bugs, more
adding new features...)
On Sun, Oct 11, 2015 at 04:55:29PM +0200, Stefan Sperling wrote:
> 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 {