mirror of
https://github.com/torvalds/linux.git
synced 2026-10-17 06:20:02 +02:00
This is another run of the Coccinelle script for converting kmalloc() family of allocations to kmalloc_obj() via the existing rules in scripts/coccinelle/api/kmalloc_objs.cocci This catches both the set of kmalloc() uses added since the first kmalloc_obj() conversions in v7.0 and adds a large group missed in the first pass due to Coccinelle not interacting well with the cleanup.h scoped_...() family of macros[1]. I worked around this with spatch's "--macro-file" argument to a file with all the scoped_...() macros mapped to Coccinelle's YACFE_ITERATOR[2] as that was the closest viable control flow indicator I could find. Build tested allmodconfig on x86, arm64, arm, loongarch, mips, powerpc, riscv, and s390 with no new warnings. Link: https://lore.kernel.org/lkml/202609021314.8A9C0B8@keescook/ [1] Link: https://github.com/coccinelle/coccinelle/blob/master/standard.h [2] Signed-off-by: Kees Cook <kees+treewide@kernel.org>
703 lines
18 KiB
C
703 lines
18 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2017-2026 Morse Micro
|
|
*/
|
|
#include <linux/gpio.h>
|
|
#include <linux/random.h>
|
|
#include <linux/timer.h>
|
|
#include <linux/bitops.h>
|
|
#include <linux/slab.h>
|
|
#include "hif.h"
|
|
#include "ps.h"
|
|
#include "bus.h"
|
|
#include "command.h"
|
|
#include "skbq.h"
|
|
|
|
/* This is a fail safe timeout */
|
|
#define CHIP_FULL_RECOVERY_TIMEOUT_MS 30
|
|
|
|
/* Defined as the max number of MPDUs per AMPDU */
|
|
#define MAX_PKTS_PER_TX_TXN 16
|
|
#define MAX_PKTS_PER_RX_TXN 32
|
|
|
|
static int mm81x_yaps_alloc_pkt_buffers(struct mm81x_yaps *yaps)
|
|
{
|
|
yaps->hw.to_chip_pkts = kzalloc_objs(*yaps->hw.to_chip_pkts,
|
|
MAX_PKTS_PER_TX_TXN);
|
|
if (!yaps->hw.to_chip_pkts)
|
|
return -ENOMEM;
|
|
|
|
yaps->hw.from_chip_pkts = kzalloc_objs(*yaps->hw.from_chip_pkts,
|
|
MAX_PKTS_PER_RX_TXN);
|
|
if (!yaps->hw.from_chip_pkts) {
|
|
kfree(yaps->hw.to_chip_pkts);
|
|
yaps->hw.to_chip_pkts = NULL;
|
|
return -ENOMEM;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void mm81x_yaps_free_pkt_buffers(struct mm81x_yaps *yaps)
|
|
{
|
|
kfree(yaps->hw.from_chip_pkts);
|
|
yaps->hw.from_chip_pkts = NULL;
|
|
kfree(yaps->hw.to_chip_pkts);
|
|
yaps->hw.to_chip_pkts = NULL;
|
|
}
|
|
|
|
static int mm81x_yaps_write_pkts(struct mm81x_yaps *yaps,
|
|
struct mm81x_yaps_pkt *pkts, int num_pkts,
|
|
int *num_pkts_sent)
|
|
{
|
|
return yaps->ops->write_pkts(yaps, pkts, num_pkts, num_pkts_sent);
|
|
}
|
|
|
|
static int mm81x_yaps_read_pkts(struct mm81x_yaps *yaps,
|
|
struct mm81x_yaps_pkt *pkts, int num_pkts_max,
|
|
int *num_pkts_received)
|
|
{
|
|
return yaps->ops->read_pkts(yaps, pkts, num_pkts_max,
|
|
num_pkts_received);
|
|
}
|
|
|
|
static int mm81x_yaps_update_status(struct mm81x_yaps *yaps)
|
|
{
|
|
return yaps->ops->update_status(yaps);
|
|
}
|
|
|
|
/* Mappings between sk_buff, skbq and yaps */
|
|
static struct mm81x_skbq *mm81x_yaps_tc_q_from_aci(struct mm81x *mors, int aci)
|
|
{
|
|
struct mm81x_yaps *yaps = &mors->hif.u.yaps;
|
|
|
|
if (aci >= ARRAY_SIZE(yaps->data_tx_qs))
|
|
return NULL;
|
|
return &yaps->data_tx_qs[aci];
|
|
}
|
|
|
|
static void mm81x_yaps_get_tx_qs(struct mm81x *mors, struct mm81x_skbq **qs,
|
|
int *num_qs)
|
|
{
|
|
*qs = mors->hif.u.yaps.data_tx_qs;
|
|
*num_qs = YAPS_TX_SKBQ_MAX;
|
|
}
|
|
|
|
static struct mm81x_skbq *mm81x_yaps_get_bcn_tc_q(struct mm81x *mors)
|
|
{
|
|
return &mors->hif.u.yaps.beacon_q;
|
|
}
|
|
|
|
static struct mm81x_skbq *mm81x_yaps_get_mgmt_tc_q(struct mm81x *mors)
|
|
{
|
|
return &mors->hif.u.yaps.mgmt_q;
|
|
}
|
|
|
|
static struct mm81x_skbq *mm81x_yaps_get_tx_cmd_queue(struct mm81x *mors)
|
|
{
|
|
return &mors->hif.u.yaps.cmd_q;
|
|
}
|
|
|
|
static int mm81x_yaps_irq_handler(struct mm81x *mors, u32 status)
|
|
{
|
|
if (status & BIT(MM81X_INT_YAPS_FC_PKT_WAITING_IRQN))
|
|
set_bit(MM81X_HIF_EVT_RX_PEND, &mors->hif.event_flags);
|
|
|
|
if (status & BIT(MM81X_INT_YAPS_FC_PACKET_FREED_UP_IRQN)) {
|
|
timer_delete_sync_try(&mors->hif.u.yaps.chip_queue_full.timer);
|
|
set_bit(MM81X_HIF_EVT_TX_PACKET_FREED_UP_PEND,
|
|
&mors->hif.event_flags);
|
|
}
|
|
|
|
queue_work(mors->chip_wq, &mors->hif_work);
|
|
return 0;
|
|
}
|
|
|
|
const struct mm81x_hif_ops mm81x_yaps_ops = {
|
|
.init = mm81x_yaps_init,
|
|
.flush_tx_data = mm81x_yaps_flush_tx_data,
|
|
.flush_cmds = mm81x_yaps_flush_cmds,
|
|
.get_tx_status_pending_count = mm81x_yaps_get_tx_status_pending_count,
|
|
.get_tx_buffered_count = mm81x_yaps_get_tx_buffered_count,
|
|
.finish = mm81x_yaps_finish,
|
|
.skbq_get_tx_qs = mm81x_yaps_get_tx_qs,
|
|
.get_tx_beacon_queue = mm81x_yaps_get_bcn_tc_q,
|
|
.get_tx_mgmt_queue = mm81x_yaps_get_mgmt_tc_q,
|
|
.get_tx_cmd_queue = mm81x_yaps_get_tx_cmd_queue,
|
|
.get_tx_data_queue = mm81x_yaps_tc_q_from_aci,
|
|
.handle_irq = mm81x_yaps_irq_handler
|
|
};
|
|
|
|
static int mm81x_yaps_read_pkt(struct mm81x_yaps *yaps, struct sk_buff *skb)
|
|
{
|
|
struct mm81x *mors = yaps->mors;
|
|
struct sk_buff_head skbq;
|
|
struct mm81x_skbq *mq = NULL;
|
|
struct mm81x_skb_hdr *hdr;
|
|
int skb_bytes_remaining;
|
|
int skb_len;
|
|
int ret = 0;
|
|
|
|
if (!skb) {
|
|
ret = -EINVAL;
|
|
goto exit_return_page;
|
|
}
|
|
|
|
__skb_queue_head_init(&skbq);
|
|
|
|
hdr = (struct mm81x_skb_hdr *)skb->data;
|
|
if (hdr->sync != MM81X_SKB_HEADER_SYNC) {
|
|
dev_err(mors->dev, "sync value error [0xAA:%d], hdr.len %d",
|
|
hdr->sync, hdr->len);
|
|
ret = -EIO;
|
|
goto exit_return_page;
|
|
}
|
|
|
|
if (yaps->mors->hif.validate_skb_checksum &&
|
|
!mm81x_skbq_validate_checksum(skb->data)) {
|
|
dev_dbg(yaps->mors->dev,
|
|
"SKB checksum is invalid hdr:[c:%02X s:%02X len:%d]",
|
|
hdr->channel, hdr->sync, hdr->len);
|
|
|
|
if (hdr->channel != MM81X_SKB_CHAN_TX_STATUS) {
|
|
ret = -EIO;
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
switch (hdr->channel) {
|
|
case MM81X_SKB_CHAN_DATA:
|
|
case MM81X_SKB_CHAN_NDP_FRAMES:
|
|
case MM81X_SKB_CHAN_TX_STATUS:
|
|
case MM81X_SKB_CHAN_DATA_NOACK:
|
|
case MM81X_SKB_CHAN_BEACON:
|
|
case MM81X_SKB_CHAN_MGMT:
|
|
mq = &yaps->data_rx_q;
|
|
break;
|
|
case MM81X_SKB_CHAN_COMMAND:
|
|
mq = &yaps->cmd_resp_q;
|
|
break;
|
|
default:
|
|
dev_err(mors->dev, "channel value error [%d]", hdr->channel);
|
|
ret = -EIO;
|
|
goto exit_return_page;
|
|
}
|
|
|
|
skb_len = sizeof(*hdr) + hdr->offset + le16_to_cpu(hdr->len);
|
|
skb_bytes_remaining = mm81x_skbq_space(mq);
|
|
|
|
if (skb_len > skb_bytes_remaining) {
|
|
dev_err(mors->dev,
|
|
"Page will not fit in SKBQ, dropping - len %d remain %d",
|
|
skb_len, skb_bytes_remaining);
|
|
ret = -ENOMEM;
|
|
/* Queue work to clear backlog */
|
|
queue_work(mors->net_wq, &mq->dispatch_work);
|
|
goto exit_return_page;
|
|
}
|
|
|
|
skb_trim(skb, skb_len);
|
|
__skb_queue_tail(&skbq, skb);
|
|
|
|
if (skb_queue_len(&skbq))
|
|
mm81x_skbq_enq(mq, &skbq);
|
|
|
|
/* push packets up in a different context */
|
|
queue_work(mors->net_wq, &mq->dispatch_work);
|
|
|
|
goto exit;
|
|
|
|
exit_return_page:
|
|
if (ret && mq) {
|
|
dev_err(mors->dev, "failed %d", ret);
|
|
mm81x_skbq_purge(mq, &skbq);
|
|
goto exit;
|
|
}
|
|
|
|
exit:
|
|
if (ret && skb)
|
|
dev_kfree_skb(skb);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int mm81x_yaps_tx(struct mm81x_yaps *yaps, struct mm81x_skbq *mq)
|
|
{
|
|
int i;
|
|
int ret = 0;
|
|
int num_skbs = 0;
|
|
int tc_pkt_idx = 0;
|
|
int num_pkts_sent = 0;
|
|
struct sk_buff *skb;
|
|
struct sk_buff_head skbq_to_send;
|
|
struct sk_buff_head skbq_sent;
|
|
struct sk_buff_head skbq_failed;
|
|
struct sk_buff *pfirst, *pnext;
|
|
struct mm81x *mors = yaps->mors;
|
|
struct mm81x_skb_hdr *hdr;
|
|
|
|
/* Check there is something on the queue */
|
|
spin_lock_bh(&mq->lock);
|
|
skb = skb_peek(&mq->skbq);
|
|
spin_unlock_bh(&mq->lock);
|
|
if (!skb)
|
|
return 0;
|
|
|
|
__skb_queue_head_init(&skbq_to_send);
|
|
__skb_queue_head_init(&skbq_sent);
|
|
__skb_queue_head_init(&skbq_failed);
|
|
|
|
if (mq == &yaps->cmd_q)
|
|
/* Purge timed-out commands (this should not happen) */
|
|
mm81x_skbq_purge(mq, &mq->pending);
|
|
else if (mq == &yaps->mgmt_q && skb_queue_len(&mq->skbq) > 0)
|
|
/*
|
|
* Purge old mgmt frames that have not been sent due to
|
|
* congestion
|
|
*/
|
|
mm81x_skbq_purge_aged(mors, mq);
|
|
|
|
num_skbs =
|
|
mm81x_skbq_deq_num_skb(mq, &skbq_to_send, MAX_PKTS_PER_TX_TXN);
|
|
|
|
skb_queue_walk_safe(&skbq_to_send, pfirst, pnext) {
|
|
enum mm81x_yaps_to_chip_q tc_queue;
|
|
|
|
hdr = (struct mm81x_skb_hdr *)pfirst->data;
|
|
switch (hdr->channel) {
|
|
case MM81X_SKB_CHAN_COMMAND:
|
|
tc_queue = MM81X_YAPS_CMD_Q;
|
|
break;
|
|
case MM81X_SKB_CHAN_BEACON:
|
|
tc_queue = MM81X_YAPS_BEACON_Q;
|
|
break;
|
|
case MM81X_SKB_CHAN_MGMT:
|
|
tc_queue = MM81X_YAPS_MGMT_Q;
|
|
break;
|
|
default:
|
|
tc_queue = MM81X_YAPS_TX_Q;
|
|
break;
|
|
}
|
|
yaps->hw.to_chip_pkts[tc_pkt_idx].tc_queue = tc_queue;
|
|
yaps->hw.to_chip_pkts[tc_pkt_idx].skb = pfirst;
|
|
tc_pkt_idx++;
|
|
}
|
|
|
|
/* Send queued packets to chip */
|
|
ret = mm81x_yaps_update_status(yaps);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = mm81x_yaps_write_pkts(yaps, yaps->hw.to_chip_pkts, tc_pkt_idx,
|
|
&num_pkts_sent);
|
|
|
|
/* Move sent packets to done queue */
|
|
for (i = 0; i < num_pkts_sent; ++i) {
|
|
pfirst = __skb_dequeue(&skbq_to_send);
|
|
__skb_queue_tail(&skbq_sent, pfirst);
|
|
}
|
|
|
|
for (i = num_pkts_sent; i < num_skbs; ++i) {
|
|
pfirst = __skb_dequeue(&skbq_to_send);
|
|
__skb_queue_tail(&skbq_failed, pfirst);
|
|
}
|
|
|
|
if (skb_queue_len(&skbq_failed) > 0) {
|
|
mm81x_skbq_enq_prepend(mq, &skbq_failed);
|
|
|
|
/* queue full, can't requeue */
|
|
if (skb_queue_len(&skbq_failed) > 0) {
|
|
dev_warn(mors->dev,
|
|
"can't requeue failed pkts, purging");
|
|
__skb_queue_purge(&skbq_failed);
|
|
}
|
|
}
|
|
|
|
if (skb_queue_len(&skbq_sent) > 0)
|
|
mm81x_skbq_tx_complete(mq, &skbq_sent);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Returns true if there are TX data pages waiting to be sent */
|
|
static bool mm81x_yaps_tx_data_handler(struct mm81x_yaps *yaps)
|
|
{
|
|
s16 aci;
|
|
u32 count = 0;
|
|
struct mm81x *mors = yaps->mors;
|
|
|
|
for (aci = MM81X_ACI_VO; aci >= 0; aci--) {
|
|
struct mm81x_skbq *data_q = mm81x_yaps_tc_q_from_aci(mors, aci);
|
|
|
|
if (!mm81x_is_data_tx_allowed(mors))
|
|
break;
|
|
|
|
yaps->chip_queue_full.is_full = mm81x_yaps_tx(yaps, data_q);
|
|
count += mm81x_skbq_count(data_q);
|
|
|
|
if (yaps->chip_queue_full.is_full)
|
|
break;
|
|
|
|
if (aci == MM81X_ACI_BE)
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Data has potentially been transmitted from the data SKBQs.
|
|
* If the mac80211 TX data Qs were previously stopped, now would
|
|
* be a good time to check if they can be started again.
|
|
*/
|
|
mm81x_skbq_may_wake_tx_queues(mors);
|
|
|
|
return (count > 0) && mm81x_is_data_tx_allowed(mors);
|
|
}
|
|
|
|
/* Returns true if there are commands waiting to be sent */
|
|
static bool mm81x_yaps_tx_cmd_handler(struct mm81x_yaps *yaps)
|
|
{
|
|
struct mm81x_skbq *cmd_q = &yaps->cmd_q;
|
|
|
|
mm81x_yaps_tx(yaps, cmd_q);
|
|
|
|
return mm81x_skbq_count(cmd_q) > 0;
|
|
}
|
|
|
|
static bool mm81x_yaps_tx_beacon_handler(struct mm81x_yaps *yaps)
|
|
{
|
|
struct mm81x_skbq *beacon_q = &yaps->beacon_q;
|
|
|
|
mm81x_yaps_tx(yaps, beacon_q);
|
|
|
|
return mm81x_skbq_count(beacon_q) > 0;
|
|
}
|
|
|
|
static bool mm81x_yaps_tx_mgmt_handler(struct mm81x_yaps *yaps)
|
|
{
|
|
struct mm81x_skbq *mgmt_q = &yaps->mgmt_q;
|
|
|
|
mm81x_yaps_tx(yaps, mgmt_q);
|
|
|
|
return mm81x_skbq_count(mgmt_q) > 0;
|
|
}
|
|
|
|
/* Returns true if there are populated RX pages left in the device */
|
|
static bool mm81x_yaps_rx_handler(struct mm81x_yaps *yaps)
|
|
{
|
|
int ret = 0;
|
|
int i;
|
|
int num_pks_received;
|
|
|
|
ret = mm81x_yaps_update_status(yaps);
|
|
if (ret)
|
|
goto exit;
|
|
|
|
ret = mm81x_yaps_read_pkts(yaps, yaps->hw.from_chip_pkts,
|
|
MAX_PKTS_PER_RX_TXN, &num_pks_received);
|
|
if (ret && ret != -EAGAIN) {
|
|
dev_err(yaps->mors->dev, "YAPS read_pkts fail: %d", ret);
|
|
goto exit;
|
|
}
|
|
|
|
for (i = 0; i < num_pks_received; ++i) {
|
|
mm81x_yaps_read_pkt(yaps, yaps->hw.from_chip_pkts[i].skb);
|
|
yaps->hw.from_chip_pkts[i].skb = NULL;
|
|
}
|
|
|
|
exit:
|
|
if (ret == -ENOMEM || ret == -EAGAIN)
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
void mm81x_yaps_stale_tx_work(struct work_struct *work)
|
|
{
|
|
int i;
|
|
int flushed = 0;
|
|
struct mm81x *mors = container_of(work, struct mm81x, tx_stale_work);
|
|
struct mm81x_yaps *yaps;
|
|
|
|
yaps = &mors->hif.u.yaps;
|
|
flushed += mm81x_skbq_check_for_stale_tx(mors, &yaps->beacon_q);
|
|
flushed += mm81x_skbq_check_for_stale_tx(mors, &yaps->mgmt_q);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(yaps->data_tx_qs); i++)
|
|
flushed += mm81x_skbq_check_for_stale_tx(mors,
|
|
&yaps->data_tx_qs[i]);
|
|
|
|
if (!flushed)
|
|
return;
|
|
|
|
dev_dbg(mors->dev, "Flushed %d stale TX SKBs", flushed);
|
|
|
|
if (mors->ps.enable && !mors->ps.suspended &&
|
|
(mm81x_yaps_get_tx_buffered_count(mors) == 0)) {
|
|
/* Evaluate ps to check if it was gated on a stale tx status */
|
|
queue_delayed_work(mors->chip_wq, &mors->ps.delayed_eval_work,
|
|
0);
|
|
}
|
|
}
|
|
|
|
void mm81x_yaps_work(struct work_struct *work)
|
|
{
|
|
struct mm81x *mors = container_of(work, struct mm81x, hif_work);
|
|
unsigned long *flags = &mors->hif.event_flags;
|
|
struct mm81x_yaps *yaps = &mors->hif.u.yaps;
|
|
|
|
if (test_bit(MM81X_STATE_CHIP_UNRESPONSIVE, &mors->state_flags))
|
|
return;
|
|
|
|
if (!*flags)
|
|
return;
|
|
|
|
/* Disable power save in case it is running */
|
|
mm81x_ps_disable(mors);
|
|
mm81x_claim_bus(mors);
|
|
|
|
/*
|
|
* Handle any populated RX pages from chip first to
|
|
* avoid dropping pkts due to full on-chip buffers.
|
|
* Check if all pages were removed, set event flags if not.
|
|
*/
|
|
if (test_and_clear_bit(MM81X_HIF_EVT_RX_PEND, flags)) {
|
|
if (mm81x_yaps_rx_handler(yaps))
|
|
set_bit(MM81X_HIF_EVT_RX_PEND, flags);
|
|
}
|
|
|
|
/* TX any commands before considering data */
|
|
if (test_and_clear_bit(MM81X_HIF_EVT_TX_COMMAND_PEND, flags)) {
|
|
if (mm81x_yaps_tx_cmd_handler(yaps))
|
|
set_bit(MM81X_HIF_EVT_TX_COMMAND_PEND, flags);
|
|
}
|
|
|
|
/* TX beacons before considering mgmt/data */
|
|
if (test_and_clear_bit(MM81X_HIF_EVT_TX_BEACON_PEND, flags)) {
|
|
if (mm81x_yaps_tx_beacon_handler(yaps))
|
|
set_bit(MM81X_HIF_EVT_TX_BEACON_PEND, flags);
|
|
}
|
|
|
|
/* TX mgmt before considering data */
|
|
if (test_and_clear_bit(MM81X_HIF_EVT_TX_MGMT_PEND, flags)) {
|
|
if (mm81x_yaps_tx_mgmt_handler(yaps))
|
|
set_bit(MM81X_HIF_EVT_TX_MGMT_PEND, flags);
|
|
}
|
|
|
|
/* Pause TX data Qs */
|
|
if (test_and_clear_bit(MM81X_HIF_EVT_DATA_TRAFFIC_PAUSE_PEND, flags)) {
|
|
test_and_clear_bit(MM81X_HIF_EVT_DATA_TRAFFIC_RESUME_PEND,
|
|
flags);
|
|
mm81x_skbq_data_traffic_pause(mors);
|
|
}
|
|
|
|
/* Resume TX data Qs */
|
|
if (test_and_clear_bit(MM81X_HIF_EVT_DATA_TRAFFIC_RESUME_PEND, flags))
|
|
mm81x_skbq_data_traffic_resume(mors);
|
|
|
|
/* Handle chip queue status */
|
|
if (test_and_clear_bit(MM81X_HIF_EVT_TX_PACKET_FREED_UP_PEND, flags))
|
|
yaps->chip_queue_full.is_full = false;
|
|
|
|
/* Check to see if the queue is full or
|
|
* long enough has past since the queue was full
|
|
*/
|
|
if (yaps->chip_queue_full.is_full &&
|
|
time_before(jiffies, yaps->chip_queue_full.retry_expiry))
|
|
goto exit;
|
|
|
|
/* Finally TX any data */
|
|
if (test_and_clear_bit(MM81X_HIF_EVT_TX_DATA_PEND, flags)) {
|
|
if (mm81x_yaps_tx_data_handler(yaps))
|
|
set_bit(MM81X_HIF_EVT_TX_DATA_PEND, flags);
|
|
|
|
if (yaps->chip_queue_full.is_full) {
|
|
yaps->chip_queue_full.retry_expiry =
|
|
jiffies +
|
|
msecs_to_jiffies(CHIP_FULL_RECOVERY_TIMEOUT_MS);
|
|
mod_timer(&yaps->chip_queue_full.timer,
|
|
yaps->chip_queue_full.retry_expiry);
|
|
}
|
|
}
|
|
|
|
exit:
|
|
|
|
/* Disable power save in case it is running */
|
|
mm81x_release_bus(mors);
|
|
mm81x_ps_enable(mors);
|
|
|
|
/* Don't requeue work if we are shutting down. */
|
|
if (yaps->finish)
|
|
return;
|
|
/*
|
|
* Evaluate all events except MM81X_HIF_EVT_TX_DATA_PEND in case data
|
|
* tx queue is full
|
|
*/
|
|
if ((*flags) & ~(1 << MM81X_HIF_EVT_TX_DATA_PEND))
|
|
queue_work(mors->chip_wq, &mors->hif_work);
|
|
/*
|
|
* if data tx queue is not full and the work hasn't been queued let's
|
|
* queue it
|
|
*/
|
|
else if (!yaps->chip_queue_full.is_full && *flags)
|
|
queue_work(mors->chip_wq, &mors->hif_work);
|
|
}
|
|
|
|
int mm81x_yaps_get_tx_status_pending_count(struct mm81x *mors)
|
|
{
|
|
int i = 0;
|
|
int count = 0;
|
|
struct mm81x_yaps *yaps;
|
|
|
|
yaps = &mors->hif.u.yaps;
|
|
count += skb_queue_len(&yaps->beacon_q.pending);
|
|
count += skb_queue_len(&yaps->mgmt_q.pending);
|
|
count += skb_queue_len(&yaps->cmd_q.pending);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(yaps->data_tx_qs); i++)
|
|
count += skb_queue_len(&yaps->data_tx_qs[i].pending);
|
|
|
|
return count;
|
|
}
|
|
|
|
int mm81x_yaps_get_tx_buffered_count(struct mm81x *mors)
|
|
{
|
|
int i = 0;
|
|
int count = 0;
|
|
struct mm81x_yaps *yaps;
|
|
|
|
yaps = &mors->hif.u.yaps;
|
|
count += skb_queue_len(&yaps->beacon_q.skbq) +
|
|
skb_queue_len(&yaps->beacon_q.pending);
|
|
count += skb_queue_len(&yaps->mgmt_q.skbq) +
|
|
skb_queue_len(&yaps->mgmt_q.pending);
|
|
count += skb_queue_len(&yaps->cmd_q.skbq) +
|
|
skb_queue_len(&yaps->cmd_q.pending);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(yaps->data_tx_qs); i++)
|
|
count += mm81x_skbq_count_tx_ready(&yaps->data_tx_qs[i]) +
|
|
skb_queue_len(&yaps->data_tx_qs[i].pending);
|
|
|
|
return count;
|
|
}
|
|
|
|
static void mm81x_yaps_tx_q_full_timer(struct timer_list *t)
|
|
{
|
|
struct mm81x_yaps *yaps =
|
|
timer_container_of(yaps, t, chip_queue_full.timer);
|
|
|
|
queue_work(yaps->mors->chip_wq, &yaps->mors->hif_work);
|
|
}
|
|
|
|
static void mm81x_yaps_q_chip_full_timer_init(struct mm81x_yaps *yaps)
|
|
{
|
|
timer_setup(&yaps->chip_queue_full.timer, mm81x_yaps_tx_q_full_timer,
|
|
0);
|
|
}
|
|
|
|
static void mm81x_yaps_q_chip_full_timer_finish(struct mm81x_yaps *yaps)
|
|
{
|
|
timer_shutdown_sync(&yaps->chip_queue_full.timer);
|
|
}
|
|
|
|
int mm81x_yaps_init(struct mm81x *mors)
|
|
{
|
|
int i, ret;
|
|
struct mm81x_yaps *yaps;
|
|
|
|
ret = mm81x_yaps_hw_init(mors);
|
|
if (ret) {
|
|
dev_err(mors->dev, "mm81x_yaps_hw_init failed %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
yaps = &mors->hif.u.yaps;
|
|
yaps->mors = mors;
|
|
|
|
mm81x_claim_bus(mors);
|
|
|
|
ret = mm81x_yaps_alloc_pkt_buffers(yaps);
|
|
if (ret) {
|
|
dev_err(mors->dev, "Failed to allocate YAPS packet buffers: %d",
|
|
ret);
|
|
mm81x_yaps_hw_finish(mors);
|
|
mm81x_release_bus(mors);
|
|
return ret;
|
|
}
|
|
|
|
/* YAPS is bi-directional */
|
|
mm81x_skbq_init(mors, &yaps->data_rx_q,
|
|
MM81X_HIF_FLAGS_DATA | MM81X_HIF_FLAGS_DIR_TO_HOST);
|
|
mm81x_skbq_init(mors, &yaps->beacon_q,
|
|
MM81X_HIF_FLAGS_DATA | MM81X_HIF_FLAGS_DIR_TO_HOST);
|
|
mm81x_skbq_init(mors, &yaps->mgmt_q,
|
|
MM81X_HIF_FLAGS_DATA | MM81X_HIF_FLAGS_DIR_TO_HOST);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(yaps->data_tx_qs); i++) {
|
|
mm81x_skbq_init(mors, &yaps->data_tx_qs[i],
|
|
MM81X_HIF_FLAGS_DATA |
|
|
MM81X_HIF_FLAGS_DIR_TO_CHIP);
|
|
}
|
|
|
|
mm81x_skbq_init(mors, &yaps->cmd_q,
|
|
MM81X_HIF_FLAGS_COMMAND | MM81X_HIF_FLAGS_DIR_TO_CHIP);
|
|
mm81x_skbq_init(mors, &yaps->cmd_resp_q,
|
|
MM81X_HIF_FLAGS_COMMAND | MM81X_HIF_FLAGS_DIR_TO_HOST);
|
|
|
|
mm81x_yaps_q_chip_full_timer_init(yaps);
|
|
INIT_WORK(&mors->hif_work, mm81x_yaps_work);
|
|
INIT_WORK(&mors->tx_stale_work, mm81x_yaps_stale_tx_work);
|
|
mm81x_release_bus(mors);
|
|
mm81x_hw_enable_stop_notifications(mors, true);
|
|
return 0;
|
|
}
|
|
|
|
void mm81x_yaps_finish(struct mm81x *mors)
|
|
{
|
|
int i;
|
|
struct mm81x_yaps *yaps;
|
|
|
|
mm81x_yaps_hw_enable_irqs(mors, false);
|
|
|
|
yaps = &mors->hif.u.yaps;
|
|
yaps->finish = true;
|
|
|
|
mm81x_skbq_finish(&yaps->data_rx_q);
|
|
mm81x_skbq_finish(&yaps->beacon_q);
|
|
mm81x_skbq_finish(&yaps->mgmt_q);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(yaps->data_tx_qs); i++)
|
|
mm81x_skbq_finish(&yaps->data_tx_qs[i]);
|
|
|
|
mm81x_skbq_finish(&yaps->cmd_q);
|
|
mm81x_skbq_finish(&yaps->cmd_resp_q);
|
|
|
|
mm81x_yaps_q_chip_full_timer_finish(yaps);
|
|
|
|
cancel_work_sync(&mors->hif_work);
|
|
cancel_work_sync(&mors->tx_stale_work);
|
|
|
|
mm81x_yaps_free_pkt_buffers(yaps);
|
|
mm81x_yaps_hw_finish(mors);
|
|
}
|
|
|
|
void mm81x_yaps_flush_tx_data(struct mm81x *mors)
|
|
{
|
|
int i;
|
|
struct mm81x_yaps *yaps = &mors->hif.u.yaps;
|
|
|
|
mm81x_skbq_tx_flush(&yaps->beacon_q);
|
|
mm81x_skbq_tx_flush(&yaps->mgmt_q);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(yaps->data_tx_qs); i++)
|
|
mm81x_skbq_tx_flush(&yaps->data_tx_qs[i]);
|
|
}
|
|
|
|
void mm81x_yaps_flush_cmds(struct mm81x *mors)
|
|
{
|
|
struct mm81x_yaps *yaps = &mors->hif.u.yaps;
|
|
|
|
if (yaps->flags & MM81X_HIF_FLAGS_COMMAND) {
|
|
mm81x_skbq_finish(&yaps->cmd_q);
|
|
mm81x_skbq_finish(&yaps->cmd_resp_q);
|
|
}
|
|
}
|