mirror of
https://github.com/torvalds/linux.git
synced 2026-05-26 08:02:27 +02:00
When wait_resp is not set but the ACK option is enabled in the MCU TXD, the ACK event is enqueued to the MCU event queue without being dequeued by the original MCU command request. Any orphaned ACK events will only be removed from the queue when another MCU command requests a response. Due to sequence index mismatches, these events are discarded one by one until a matching sequence index is found. However, if several MCU commands that do not require a response continue to fill up the event queue, there is a risk that when an MCU command with wait_resp enabled is issued, it may dequeue the wrong event skb, especially if the queue contains events with all possible sequence indices. Signed-off-by: StanleyYP Wang <StanleyYP.Wang@mediatek.com> Signed-off-by: Shayne Chen <shayne.chen@mediatek.com> Link: https://patch.msgid.link/20260203155532.1098290-3-shayne.chen@mediatek.com Signed-off-by: Felix Fietkau <nbd@nbd.name>
162 lines
3.6 KiB
C
162 lines
3.6 KiB
C
// SPDX-License-Identifier: BSD-3-Clause-Clear
|
|
/*
|
|
* Copyright (C) 2019 Lorenzo Bianconi <lorenzo.bianconi83@gmail.com>
|
|
*/
|
|
|
|
#include "mt76.h"
|
|
|
|
struct sk_buff *
|
|
__mt76_mcu_msg_alloc(struct mt76_dev *dev, const void *data,
|
|
int len, int data_len, gfp_t gfp)
|
|
{
|
|
const struct mt76_mcu_ops *ops = dev->mcu_ops;
|
|
struct sk_buff *skb;
|
|
|
|
len = max_t(int, len, data_len);
|
|
len = ops->headroom + len + ops->tailroom;
|
|
|
|
skb = alloc_skb(len, gfp);
|
|
if (!skb)
|
|
return NULL;
|
|
|
|
memset(skb->head, 0, len);
|
|
skb_reserve(skb, ops->headroom);
|
|
|
|
if (data && data_len)
|
|
skb_put_data(skb, data, data_len);
|
|
|
|
return skb;
|
|
}
|
|
EXPORT_SYMBOL_GPL(__mt76_mcu_msg_alloc);
|
|
|
|
struct sk_buff *mt76_mcu_get_response(struct mt76_dev *dev,
|
|
unsigned long expires)
|
|
{
|
|
unsigned long timeout;
|
|
|
|
if (!time_is_after_jiffies(expires))
|
|
return NULL;
|
|
|
|
timeout = expires - jiffies;
|
|
wait_event_timeout(dev->mcu.wait,
|
|
(!skb_queue_empty(&dev->mcu.res_q) ||
|
|
test_bit(MT76_MCU_RESET, &dev->phy.state)),
|
|
timeout);
|
|
return skb_dequeue(&dev->mcu.res_q);
|
|
}
|
|
EXPORT_SYMBOL_GPL(mt76_mcu_get_response);
|
|
|
|
void mt76_mcu_rx_event(struct mt76_dev *dev, struct sk_buff *skb)
|
|
{
|
|
skb_queue_tail(&dev->mcu.res_q, skb);
|
|
wake_up(&dev->mcu.wait);
|
|
}
|
|
EXPORT_SYMBOL_GPL(mt76_mcu_rx_event);
|
|
|
|
int mt76_mcu_send_and_get_msg(struct mt76_dev *dev, int cmd, const void *data,
|
|
int len, bool wait_resp, struct sk_buff **ret_skb)
|
|
{
|
|
struct sk_buff *skb;
|
|
|
|
if (dev->mcu_ops->mcu_send_msg)
|
|
return dev->mcu_ops->mcu_send_msg(dev, cmd, data, len, wait_resp);
|
|
|
|
skb = mt76_mcu_msg_alloc(dev, data, len);
|
|
if (!skb)
|
|
return -ENOMEM;
|
|
|
|
return mt76_mcu_skb_send_and_get_msg(dev, skb, cmd, wait_resp, ret_skb);
|
|
}
|
|
EXPORT_SYMBOL_GPL(mt76_mcu_send_and_get_msg);
|
|
|
|
int mt76_mcu_skb_send_and_get_msg(struct mt76_dev *dev, struct sk_buff *skb,
|
|
int cmd, bool wait_resp,
|
|
struct sk_buff **ret_skb)
|
|
{
|
|
unsigned int retry = 0;
|
|
struct sk_buff *orig_skb = NULL;
|
|
unsigned long expires;
|
|
int ret, seq;
|
|
|
|
if (mt76_is_sdio(dev))
|
|
if (test_bit(MT76_RESET, &dev->phy.state) && atomic_read(&dev->bus_hung))
|
|
return -EIO;
|
|
|
|
if (ret_skb)
|
|
*ret_skb = NULL;
|
|
|
|
mutex_lock(&dev->mcu.mutex);
|
|
|
|
if (dev->mcu_ops->mcu_skb_prepare_msg) {
|
|
orig_skb = skb;
|
|
ret = dev->mcu_ops->mcu_skb_prepare_msg(dev, skb, cmd, &seq);
|
|
if (ret < 0)
|
|
goto out;
|
|
}
|
|
|
|
retry:
|
|
/* orig skb might be needed for retry, mcu_skb_send_msg consumes it */
|
|
if (orig_skb)
|
|
skb_get(orig_skb);
|
|
ret = dev->mcu_ops->mcu_skb_send_msg(dev, skb, cmd, wait_resp ? &seq : NULL);
|
|
if (ret < 0)
|
|
goto out;
|
|
|
|
if (!wait_resp) {
|
|
ret = 0;
|
|
goto out;
|
|
}
|
|
|
|
expires = jiffies + dev->mcu.timeout;
|
|
|
|
do {
|
|
skb = mt76_mcu_get_response(dev, expires);
|
|
if (!skb && !test_bit(MT76_MCU_RESET, &dev->phy.state) &&
|
|
orig_skb && retry++ < dev->mcu_ops->max_retry) {
|
|
dev_err(dev->dev, "Retry message %08x (seq %d)\n",
|
|
cmd, seq);
|
|
skb = orig_skb;
|
|
goto retry;
|
|
}
|
|
|
|
ret = dev->mcu_ops->mcu_parse_response(dev, cmd, skb, seq);
|
|
if (!ret && ret_skb)
|
|
*ret_skb = skb;
|
|
else
|
|
dev_kfree_skb(skb);
|
|
} while (ret == -EAGAIN);
|
|
|
|
|
|
out:
|
|
dev_kfree_skb(orig_skb);
|
|
mutex_unlock(&dev->mcu.mutex);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(mt76_mcu_skb_send_and_get_msg);
|
|
|
|
int __mt76_mcu_send_firmware(struct mt76_dev *dev, int cmd, const void *data,
|
|
int len, int max_len)
|
|
{
|
|
int err, cur_len;
|
|
|
|
while (len > 0) {
|
|
cur_len = min_t(int, max_len, len);
|
|
|
|
err = mt76_mcu_send_msg(dev, cmd, data, cur_len, false);
|
|
if (err)
|
|
return err;
|
|
|
|
data += cur_len;
|
|
len -= cur_len;
|
|
|
|
if (dev->queue_ops->tx_cleanup)
|
|
dev->queue_ops->tx_cleanup(dev,
|
|
dev->q_mcu[MT_MCUQ_FWDL],
|
|
false);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(__mt76_mcu_send_firmware);
|