blob: 2d1728c1a6dece800786c62e6543e7b9e89a8206 [file]
// 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);
}
}