diff options
author | Johannes Berg <johannes.berg@intel.com> | 2012-03-15 16:26:43 -0400 |
---|---|---|
committer | John W. Linville <linville@tuxdriver.com> | 2012-04-09 16:37:14 -0400 |
commit | 0c19744c344cf1bfda04f681ff4e1e46455577bd (patch) | |
tree | d6351ac483da68ca9bfe46df5642dccef313f17a /drivers/net/wireless/iwlwifi/iwl-trans-pcie-rx.c | |
parent | 88f10a176c7364a700c8638732e2b3110beaceb6 (diff) |
iwlwifi: process multiple frames per RXB
The flow handler (hardware) can put multiple
frames into a single RX buffer. To handle
this, walk the RX buffer and check if there
are multiple valid packets in it.
To let the upper layer handle this correctly
introduce rxb_offset() which is needed when
we pass pages to mac80211 -- we need to know
the offset into the page there.
Also change the page handling scheme to use
refcounting. Anyone who needs a page will
"steal" it, which marks it as having been
used & refcounts it. The RX handler then has
to free its own reference and must not reuse
the page.
Finally, do not set the bit asking the FH to
give us each packet in a single buffer. This
really enables the feature.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: Wey-Yi Guy <wey-yi.w.guy@intel.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
Diffstat (limited to 'drivers/net/wireless/iwlwifi/iwl-trans-pcie-rx.c')
-rw-r--r-- | drivers/net/wireless/iwlwifi/iwl-trans-pcie-rx.c | 129 |
1 files changed, 71 insertions, 58 deletions
diff --git a/drivers/net/wireless/iwlwifi/iwl-trans-pcie-rx.c b/drivers/net/wireless/iwlwifi/iwl-trans-pcie-rx.c index 8b1a7988e176..b28231196342 100644 --- a/drivers/net/wireless/iwlwifi/iwl-trans-pcie-rx.c +++ b/drivers/net/wireless/iwlwifi/iwl-trans-pcie-rx.c | |||
@@ -362,83 +362,96 @@ static void iwl_rx_handle_rxbuf(struct iwl_trans *trans, | |||
362 | struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); | 362 | struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans); |
363 | struct iwl_rx_queue *rxq = &trans_pcie->rxq; | 363 | struct iwl_rx_queue *rxq = &trans_pcie->rxq; |
364 | struct iwl_tx_queue *txq = &trans_pcie->txq[trans_pcie->cmd_queue]; | 364 | struct iwl_tx_queue *txq = &trans_pcie->txq[trans_pcie->cmd_queue]; |
365 | struct iwl_device_cmd *cmd; | ||
366 | unsigned long flags; | 365 | unsigned long flags; |
367 | int len, err; | 366 | bool page_stolen = false; |
368 | u16 sequence; | 367 | int max_len = PAGE_SIZE << hw_params(trans).rx_page_order; |
369 | struct iwl_rx_cmd_buffer rxcb; | 368 | u32 offset = 0; |
370 | struct iwl_rx_packet *pkt; | ||
371 | bool reclaim; | ||
372 | int index, cmd_index; | ||
373 | 369 | ||
374 | if (WARN_ON(!rxb)) | 370 | if (WARN_ON(!rxb)) |
375 | return; | 371 | return; |
376 | 372 | ||
377 | dma_unmap_page(trans->dev, rxb->page_dma, | 373 | dma_unmap_page(trans->dev, rxb->page_dma, max_len, DMA_FROM_DEVICE); |
378 | PAGE_SIZE << hw_params(trans).rx_page_order, | ||
379 | DMA_FROM_DEVICE); | ||
380 | 374 | ||
381 | rxcb._page = rxb->page; | 375 | while (offset + sizeof(u32) + sizeof(struct iwl_cmd_header) < max_len) { |
382 | pkt = rxb_addr(&rxcb); | 376 | struct iwl_rx_packet *pkt; |
377 | struct iwl_device_cmd *cmd; | ||
378 | u16 sequence; | ||
379 | bool reclaim; | ||
380 | int index, cmd_index, err, len; | ||
381 | struct iwl_rx_cmd_buffer rxcb = { | ||
382 | ._offset = offset, | ||
383 | ._page = rxb->page, | ||
384 | ._page_stolen = false, | ||
385 | }; | ||
383 | 386 | ||
384 | IWL_DEBUG_RX(trans, "%s, 0x%02x\n", | 387 | pkt = rxb_addr(&rxcb); |
385 | get_cmd_string(pkt->hdr.cmd), pkt->hdr.cmd); | ||
386 | 388 | ||
389 | if (pkt->len_n_flags == cpu_to_le32(FH_RSCSR_FRAME_INVALID)) | ||
390 | break; | ||
387 | 391 | ||
388 | len = le32_to_cpu(pkt->len_n_flags) & FH_RSCSR_FRAME_SIZE_MSK; | 392 | IWL_DEBUG_RX(trans, "cmd at offset %d: %s (0x%.2x)\n", |
389 | len += sizeof(u32); /* account for status word */ | 393 | rxcb._offset, get_cmd_string(pkt->hdr.cmd), |
390 | trace_iwlwifi_dev_rx(trans->dev, pkt, len); | 394 | pkt->hdr.cmd); |
391 | 395 | ||
392 | /* Reclaim a command buffer only if this packet is a response | 396 | len = le32_to_cpu(pkt->len_n_flags) & FH_RSCSR_FRAME_SIZE_MSK; |
393 | * to a (driver-originated) command. | 397 | len += sizeof(u32); /* account for status word */ |
394 | * If the packet (e.g. Rx frame) originated from uCode, | 398 | trace_iwlwifi_dev_rx(trans->dev, pkt, len); |
395 | * there is no command buffer to reclaim. | 399 | |
396 | * Ucode should set SEQ_RX_FRAME bit if ucode-originated, | 400 | /* Reclaim a command buffer only if this packet is a response |
397 | * but apparently a few don't get set; catch them here. */ | 401 | * to a (driver-originated) command. |
398 | reclaim = !(pkt->hdr.sequence & SEQ_RX_FRAME); | 402 | * If the packet (e.g. Rx frame) originated from uCode, |
399 | if (reclaim) { | 403 | * there is no command buffer to reclaim. |
400 | int i; | 404 | * Ucode should set SEQ_RX_FRAME bit if ucode-originated, |
401 | 405 | * but apparently a few don't get set; catch them here. */ | |
402 | for (i = 0; i < trans_pcie->n_no_reclaim_cmds; i++) { | 406 | reclaim = !(pkt->hdr.sequence & SEQ_RX_FRAME); |
403 | if (trans_pcie->no_reclaim_cmds[i] == pkt->hdr.cmd) { | 407 | if (reclaim) { |
404 | reclaim = false; | 408 | int i; |
405 | break; | 409 | |
410 | for (i = 0; i < trans_pcie->n_no_reclaim_cmds; i++) { | ||
411 | if (trans_pcie->no_reclaim_cmds[i] == | ||
412 | pkt->hdr.cmd) { | ||
413 | reclaim = false; | ||
414 | break; | ||
415 | } | ||
406 | } | 416 | } |
407 | } | 417 | } |
408 | } | ||
409 | 418 | ||
410 | sequence = le16_to_cpu(pkt->hdr.sequence); | 419 | sequence = le16_to_cpu(pkt->hdr.sequence); |
411 | index = SEQ_TO_INDEX(sequence); | 420 | index = SEQ_TO_INDEX(sequence); |
412 | cmd_index = get_cmd_index(&txq->q, index); | 421 | cmd_index = get_cmd_index(&txq->q, index); |
413 | 422 | ||
414 | if (reclaim) | 423 | if (reclaim) |
415 | cmd = txq->cmd[cmd_index]; | 424 | cmd = txq->cmd[cmd_index]; |
416 | else | 425 | else |
417 | cmd = NULL; | 426 | cmd = NULL; |
418 | 427 | ||
419 | err = iwl_op_mode_rx(trans->op_mode, &rxcb, cmd); | 428 | err = iwl_op_mode_rx(trans->op_mode, &rxcb, cmd); |
420 | 429 | ||
421 | /* | 430 | /* |
422 | * XXX: After here, we should always check rxcb._page | 431 | * After here, we should always check rxcb._page_stolen, |
423 | * against NULL before touching it or its virtual | 432 | * if it is true then one of the handlers took the page. |
424 | * memory (pkt). Because some rx_handler might have | 433 | */ |
425 | * already taken or freed the pages. | ||
426 | */ | ||
427 | 434 | ||
428 | if (reclaim) { | 435 | if (reclaim) { |
429 | /* Invoke any callbacks, transfer the buffer to caller, | 436 | /* Invoke any callbacks, transfer the buffer to caller, |
430 | * and fire off the (possibly) blocking | 437 | * and fire off the (possibly) blocking |
431 | * iwl_trans_send_cmd() | 438 | * iwl_trans_send_cmd() |
432 | * as we reclaim the driver command queue */ | 439 | * as we reclaim the driver command queue */ |
433 | if (rxcb._page) | 440 | if (!rxcb._page_stolen) |
434 | iwl_tx_cmd_complete(trans, &rxcb, err); | 441 | iwl_tx_cmd_complete(trans, &rxcb, err); |
435 | else | 442 | else |
436 | IWL_WARN(trans, "Claim null rxb?\n"); | 443 | IWL_WARN(trans, "Claim null rxb?\n"); |
444 | } | ||
445 | |||
446 | page_stolen |= rxcb._page_stolen; | ||
447 | offset += ALIGN(len, FH_RSCSR_FRAME_ALIGN); | ||
437 | } | 448 | } |
438 | 449 | ||
439 | /* page was stolen from us */ | 450 | /* page was stolen from us -- free our reference */ |
440 | if (rxcb._page == NULL) | 451 | if (page_stolen) { |
452 | __free_pages(rxb->page, hw_params(trans).rx_page_order); | ||
441 | rxb->page = NULL; | 453 | rxb->page = NULL; |
454 | } | ||
442 | 455 | ||
443 | /* Reuse the page if possible. For notification packets and | 456 | /* Reuse the page if possible. For notification packets and |
444 | * SKBs that fail to Rx correctly, add them back into the | 457 | * SKBs that fail to Rx correctly, add them back into the |