In RX enhance mode (WHCR bit 16), the chip appends the next interrupt status block to every RX data read: after the frames come 4 bytes of zero padding and then the same 112-byte block a WHISR read returns. mt76 never enabled it, so the txrx worker read WHISR before every RX read, about two status reads per frame. MediaTek's own driver for these chips runs in enhance mode and only reads WHISR when no appended status is pending. Do the same for mt7663s: read the extra 116 bytes with the frames, keep the appended status when its padding is zero, and let the next parse_irq() use it instead of reading WHISR. Each status is used once, so the read-clear TX quota counts are neither lost nor counted twice. mt7663s reads only RX port 0, so a pass of the worker makes at most one RX read and no appended status is overwritten before it is used. A 1.5 KB frame plus the status still fits in four 512-byte blocks, so the reads don't grow. Measured with ftrace on an MT7663 SDIO module under a UDP stream: 881 WHISR reads for 857 RX reads (2.1 per RX read before), with TCP and UDP throughput unchanged on that air-limited link, no added loss, and no TX stalls over 30 s of transmit. Signed-off-by: Ryan Brue --- drivers/net/wireless/mediatek/mt76/mt76.h | 10 +++++++++ drivers/net/wireless/mediatek/mt76/mt7615/sdio.c | 28 +++++++++++++++++++----- drivers/net/wireless/mediatek/mt76/sdio.c | 3 +++ drivers/net/wireless/mediatek/mt76/sdio_txrx.c | 15 +++++++++++-- 4 files changed, 48 insertions(+), 8 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h index 9e851dea71d9..9c637ebddb52 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76.h +++ b/drivers/net/wireless/mediatek/mt76/mt76.h @@ -700,6 +700,16 @@ struct mt76_sdio { struct sdio_func *func; void *intr_data; u8 hw_ver; + + /* + * RX enhance mode (CONNAC1): the chip appends the next interrupt + * status (intr_size bytes, after 4 bytes of zero padding) to every + * RX data read, saving a separate status read while frames stream. + */ + bool rx_enhance; + bool intr_pending; + u16 intr_size; + void *intr_next; wait_queue_head_t wait; int pse_mcu_quota_max; diff --git a/drivers/net/wireless/mediatek/mt76/mt7615/sdio.c b/drivers/net/wireless/mediatek/mt76/mt7615/sdio.c index 46188951ad19..f744240ebd34 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7615/sdio.c +++ b/drivers/net/wireless/mediatek/mt76/mt7615/sdio.c @@ -56,12 +56,19 @@ static int mt7663s_parse_intr(struct mt76_dev *dev, struct mt76s_intr *intr) struct mt7663s_intr *irq_data = sdio->intr_data; int i, err; - sdio_claim_host(sdio->func); - err = sdio_readsb(sdio->func, irq_data, MCR_WHISR, sizeof(*irq_data)); - sdio_release_host(sdio->func); - - if (err) - return err; + if (sdio->intr_pending) { + /* appended to the last RX read (RX enhance mode) */ + memcpy(irq_data, sdio->intr_next, sizeof(*irq_data)); + sdio->intr_pending = false; + } else { + sdio_claim_host(sdio->func); + err = sdio_readsb(sdio->func, irq_data, MCR_WHISR, + sizeof(*irq_data)); + sdio_release_host(sdio->func); + + if (err) + return err; + } intr->isr = irq_data->isr; intr->rec_mb = irq_data->rec_mb; @@ -124,6 +131,15 @@ static int mt7663s_probe(struct sdio_func *func, if (ret < 0) goto error; + mdev->sdio.intr_size = sizeof(struct mt7663s_intr); + mdev->sdio.intr_next = devm_kmalloc(mdev->dev, mdev->sdio.intr_size, + GFP_KERNEL); + if (!mdev->sdio.intr_next) { + ret = -ENOMEM; + goto error; + } + mdev->sdio.rx_enhance = true; + ret = mt76s_hw_init(mdev, func, MT76_CONNAC_SDIO); if (ret) goto error; diff --git a/drivers/net/wireless/mediatek/mt76/sdio.c b/drivers/net/wireless/mediatek/mt76/sdio.c index ba5f123f7e39..95af0a90546c 100644 --- a/drivers/net/wireless/mediatek/mt76/sdio.c +++ b/drivers/net/wireless/mediatek/mt76/sdio.c @@ -269,6 +269,9 @@ int mt76s_hw_init(struct mt76_dev *dev, struct sdio_func *func, int hw_ver) case MT76_CONNAC_SDIO: /* set WHISR as read clear and Rx aggregation number as 16 */ ctrl = FIELD_PREP(MAX_HIF_RX_LEN_NUM, 16); + if (dev->sdio.rx_enhance) + ctrl |= RX_ENHANCE_MODE; + dev->sdio.intr_pending = false; break; default: ctrl = sdio_readl(func, MCR_WHCR, &ret); diff --git a/drivers/net/wireless/mediatek/mt76/sdio_txrx.c b/drivers/net/wireless/mediatek/mt76/sdio_txrx.c index 311c31e87ee5..f15746a13991 100644 --- a/drivers/net/wireless/mediatek/mt76/sdio_txrx.c +++ b/drivers/net/wireless/mediatek/mt76/sdio_txrx.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -87,7 +88,7 @@ mt76s_rx_run_queue(struct mt76_dev *dev, enum mt76_rxq_id qid, { struct mt76_queue *q = &dev->q_rx[qid]; struct mt76_sdio *sdio = &dev->sdio; - int len = 0, err, i; + int len = 0, data_len, err, i; struct page *page; u8 *buf, *end; @@ -97,6 +98,10 @@ mt76s_rx_run_queue(struct mt76_dev *dev, enum mt76_rxq_id qid, if (!len) return 0; + data_len = len; + if (sdio->rx_enhance) + len += 4 + sdio->intr_size; + if (len > sdio->func->cur_blksize) len = roundup(len, sdio->func->cur_blksize); @@ -117,7 +122,13 @@ mt76s_rx_run_queue(struct mt76_dev *dev, enum mt76_rxq_id qid, return err; } - end = buf + len; + /* The next interrupt status follows the frames and a zero pad */ + if (sdio->rx_enhance && !get_unaligned((u32 *)(buf + data_len))) { + memcpy(sdio->intr_next, buf + data_len + 4, sdio->intr_size); + sdio->intr_pending = true; + } + + end = buf + data_len; i = 0; while (i < intr->rx.num[qid] && buf < end) { -- 2.55.0