root/drivers/net/wireless/intel/iwlwifi/mei/main.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (C) 2021-2024 Intel Corporation
 */

#include <linux/etherdevice.h>
#include <linux/netdevice.h>
#include <linux/ieee80211.h>
#include <linux/rtnetlink.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/mei_cl_bus.h>
#include <linux/rcupdate.h>
#include <linux/debugfs.h>
#include <linux/skbuff.h>
#include <linux/wait.h>
#include <linux/slab.h>
#include <linux/mm.h>

#include <net/cfg80211.h>

#include "internal.h"
#include "iwl-mei.h"
#include "trace.h"
#include "trace-data.h"
#include "sap.h"

MODULE_DESCRIPTION("The Intel(R) wireless / CSME firmware interface");
MODULE_LICENSE("GPL");

#define MEI_WLAN_UUID UUID_LE(0x13280904, 0x7792, 0x4fcb, \
                              0xa1, 0xaa, 0x5e, 0x70, 0xcb, 0xb1, 0xe8, 0x65)

/* After CSME takes ownership, it won't release it for 60 seconds to avoid
 * frequent ownership transitions.
 */
#define MEI_OWNERSHIP_RETAKE_TIMEOUT_MS msecs_to_jiffies(60000)

/*
 * Since iwlwifi calls iwlmei without any context, hold a pointer to the
 * mei_cl_device structure here.
 * Define a mutex that will synchronize all the flows between iwlwifi and
 * iwlmei.
 * Note that iwlmei can't have several instances, so it ok to have static
 * variables here.
 */
static struct mei_cl_device *iwl_mei_global_cldev;
static DEFINE_MUTEX(iwl_mei_mutex);
static unsigned long iwl_mei_status;

enum iwl_mei_status_bits {
        IWL_MEI_STATUS_SAP_CONNECTED,
};

bool iwl_mei_is_connected(void)
{
        return test_bit(IWL_MEI_STATUS_SAP_CONNECTED, &iwl_mei_status);
}
EXPORT_SYMBOL_GPL(iwl_mei_is_connected);

#define SAP_CONTROL_BLOCK_ID 0x21504153 /* SAP! in ASCII */

struct iwl_sap_q_ctrl_blk {
        __le32 wr_ptr;
        __le32 rd_ptr;
        __le32 size;
};

enum iwl_sap_q_idx {
        SAP_QUEUE_IDX_NOTIF = 0,
        SAP_QUEUE_IDX_DATA,
        SAP_QUEUE_IDX_MAX,
};

struct iwl_sap_dir {
        __le32 reserved;
        struct iwl_sap_q_ctrl_blk q_ctrl_blk[SAP_QUEUE_IDX_MAX];
};

enum iwl_sap_dir_idx {
        SAP_DIRECTION_HOST_TO_ME = 0,
        SAP_DIRECTION_ME_TO_HOST,
        SAP_DIRECTION_MAX,
};

struct iwl_sap_shared_mem_ctrl_blk {
        __le32 sap_id;
        __le32 size;
        struct iwl_sap_dir dir[SAP_DIRECTION_MAX];
};

/*
 * The shared area has the following layout:
 *
 * +-----------------------------------+
 * |struct iwl_sap_shared_mem_ctrl_blk |
 * +-----------------------------------+
 * |Host -> ME data queue              |
 * +-----------------------------------+
 * |Host -> ME notif queue             |
 * +-----------------------------------+
 * |ME -> Host data queue              |
 * +-----------------------------------+
 * |ME -> host notif queue             |
 * +-----------------------------------+
 * |SAP control block id (SAP!)        |
 * +-----------------------------------+
 */

#define SAP_H2M_DATA_Q_SZ       48256
#define SAP_M2H_DATA_Q_SZ       24128
#define SAP_H2M_NOTIF_Q_SZ_VER3 2240
#define SAP_H2M_NOTIF_Q_SZ_VER4 32768
#define SAP_M2H_NOTIF_Q_SZ      62720

#define _IWL_MEI_SAP_SHARED_MEM_SZ_VER3 \
        (sizeof(struct iwl_sap_shared_mem_ctrl_blk) + \
         SAP_H2M_DATA_Q_SZ + SAP_H2M_NOTIF_Q_SZ_VER3 + \
         SAP_M2H_DATA_Q_SZ + SAP_M2H_NOTIF_Q_SZ + 4)

#define _IWL_MEI_SAP_SHARED_MEM_SZ_VER4 \
        (sizeof(struct iwl_sap_shared_mem_ctrl_blk) + \
         SAP_H2M_DATA_Q_SZ + SAP_H2M_NOTIF_Q_SZ_VER4 + \
         SAP_M2H_DATA_Q_SZ + SAP_M2H_NOTIF_Q_SZ + 4)

struct iwl_mei_shared_mem_ptrs {
        struct iwl_sap_shared_mem_ctrl_blk *ctrl;
        void *q_head[SAP_DIRECTION_MAX][SAP_QUEUE_IDX_MAX];
        size_t q_size[SAP_DIRECTION_MAX][SAP_QUEUE_IDX_MAX];
};

struct iwl_mei_filters {
        struct rcu_head rcu_head;
        struct iwl_sap_oob_filters filters;
};

/**
 * struct iwl_mei - holds the private date for iwl_mei
 *
 * @get_nvm_wq: the wait queue for the get_nvm flow
 * @send_csa_msg_wk: used to defer the transmission of the CHECK_SHARED_AREA
 *      message. Used so that we can send CHECK_SHARED_AREA from atomic
 *      contexts.
 * @get_ownership_wq: the wait queue for the get_ownership_flow
 * @shared_mem: the memory that is shared between CSME and the host
 * @cldev: the pointer to the MEI client device
 * @nvm: the data returned by the CSME for the NVM
 * @filters: the filters sent by CSME
 * @got_ownership: true if we own the device
 * @amt_enabled: true if CSME has wireless enabled
 * @csa_throttled: when true, we can't send CHECK_SHARED_AREA over the MEI
 *      bus, but rather need to wait until send_csa_msg_wk runs
 * @csme_taking_ownership: true when CSME is taking ownership. Used to remember
 *      to send CSME_OWNERSHIP_CONFIRMED when the driver completes its down
 *      flow.
 * @link_prot_state: true when we are in link protection PASSIVE
 * @device_down: true if the device is down. Used to remember to send
 *      CSME_OWNERSHIP_CONFIRMED when the driver is already down.
 * @csa_throttle_end_wk: used when &csa_throttled is true
 * @pldr_wq: the wait queue for PLDR flow
 * @pldr_active: PLDR flow is in progress
 * @data_q_lock: protects the access to the data queues which are
 *      accessed without the mutex.
 * @netdev_work: used to defer registering and unregistering of the netdev to
 *      avoid taking the rtnl lock in the SAP messages handlers.
 * @ownership_dwork: used to re-ask for NIC ownership after ownership was taken
 *      by CSME or when a previous ownership request failed.
 * @sap_seq_no: the sequence number for the SAP messages
 * @seq_no: the sequence number for the SAP messages
 * @dbgfs_dir: the debugfs dir entry
 */
struct iwl_mei {
        wait_queue_head_t get_nvm_wq;
        struct work_struct send_csa_msg_wk;
        wait_queue_head_t get_ownership_wq;
        struct iwl_mei_shared_mem_ptrs shared_mem;
        struct mei_cl_device *cldev;
        struct iwl_mei_nvm *nvm;
        struct iwl_mei_filters __rcu *filters;
        bool got_ownership;
        bool amt_enabled;
        bool csa_throttled;
        bool csme_taking_ownership;
        bool link_prot_state;
        bool device_down;
        struct delayed_work csa_throttle_end_wk;
        wait_queue_head_t pldr_wq;
        bool pldr_active;
        spinlock_t data_q_lock;
        struct work_struct netdev_work;
        struct delayed_work ownership_dwork;

        atomic_t sap_seq_no;
        atomic_t seq_no;

        struct dentry *dbgfs_dir;
};

/**
 * struct iwl_mei_cache - cache for the parameters from iwlwifi
 * @ops: Callbacks to iwlwifi.
 * @netdev: The netdev that will be used to transmit / receive packets.
 * @conn_info: The connection info message triggered by iwlwifi's association.
 * @power_limit: pointer to an array of 10 elements (le16) represents the power
 *      restrictions per chain.
 * @rf_kill: rf kill state.
 * @mcc: MCC info
 * @mac_address: interface MAC address.
 * @nvm_address: NVM MAC address.
 * @priv: A pointer to iwlwifi.
 * @sap_version: The SAP version to use. enum iwl_mei_sap_version.
 *
 * This used to cache the configurations coming from iwlwifi's way. The data
 * is cached here so that we can buffer the configuration even if we don't have
 * a bind from the mei bus and hence, on iwl_mei structure.
 */
struct iwl_mei_cache {
        const struct iwl_mei_ops *ops;
        struct net_device __rcu *netdev;
        const struct iwl_sap_notif_connection_info *conn_info;
        const __le16 *power_limit;
        u32 rf_kill;
        u16 mcc;
        u8 mac_address[6];
        u8 nvm_address[6];
        enum iwl_mei_sap_version sap_version;
        void *priv;
};

static struct iwl_mei_cache iwl_mei_cache = {
        .rf_kill = SAP_HW_RFKILL_DEASSERTED | SAP_SW_RFKILL_DEASSERTED
};

static void iwl_mei_free_shared_mem(struct mei_cl_device *cldev)
{
        struct iwl_mei *mei = mei_cldev_get_drvdata(cldev);

        if (mei_cldev_dma_unmap(cldev))
                dev_err(&cldev->dev, "Couldn't unmap the shared mem properly\n");
        memset(&mei->shared_mem, 0, sizeof(mei->shared_mem));
}

#define HBM_DMA_BUF_ID_WLAN 1

static int iwl_mei_alloc_mem_for_version(struct mei_cl_device *cldev,
                                         enum iwl_mei_sap_version version)
{
        struct iwl_mei *mei = mei_cldev_get_drvdata(cldev);
        struct iwl_mei_shared_mem_ptrs *mem = &mei->shared_mem;
        u32 mem_size = roundup(version == IWL_MEI_SAP_VERSION_4 ?
                               _IWL_MEI_SAP_SHARED_MEM_SZ_VER4 :
                               _IWL_MEI_SAP_SHARED_MEM_SZ_VER3, PAGE_SIZE);

        iwl_mei_cache.sap_version = version;
        mem->ctrl = mei_cldev_dma_map(cldev, HBM_DMA_BUF_ID_WLAN, mem_size);
        if (IS_ERR(mem->ctrl)) {
                int ret = PTR_ERR(mem->ctrl);

                mem->ctrl = NULL;

                return ret;
        }

        memset(mem->ctrl, 0, mem_size);

        return 0;
}

static int iwl_mei_alloc_shared_mem(struct mei_cl_device *cldev)
{
        int ret;

        /*
         * SAP version 4 uses a larger Host to MEI notif queue.
         * Since it is unknown at this stage which SAP version is used by the
         * CSME firmware on this platform, try to allocate the version 4 first.
         * If the CSME firmware uses version 3, this allocation is expected to
         * fail because the CSME firmware allocated less memory for our driver.
         */
        ret = iwl_mei_alloc_mem_for_version(cldev, IWL_MEI_SAP_VERSION_4);
        if (ret)
                ret = iwl_mei_alloc_mem_for_version(cldev,
                                                    IWL_MEI_SAP_VERSION_3);

        return ret;
}

static void iwl_mei_init_shared_mem(struct iwl_mei *mei)
{
        struct iwl_mei_shared_mem_ptrs *mem = &mei->shared_mem;
        struct iwl_sap_dir *h2m;
        struct iwl_sap_dir *m2h;
        int dir, queue;
        u8 *q_head;

        mem->ctrl->sap_id = cpu_to_le32(SAP_CONTROL_BLOCK_ID);

        mem->ctrl->size = cpu_to_le32(sizeof(*mem->ctrl));

        h2m = &mem->ctrl->dir[SAP_DIRECTION_HOST_TO_ME];
        m2h = &mem->ctrl->dir[SAP_DIRECTION_ME_TO_HOST];

        h2m->q_ctrl_blk[SAP_QUEUE_IDX_DATA].size =
                cpu_to_le32(SAP_H2M_DATA_Q_SZ);
        h2m->q_ctrl_blk[SAP_QUEUE_IDX_NOTIF].size =
                iwl_mei_cache.sap_version == IWL_MEI_SAP_VERSION_3 ?
                cpu_to_le32(SAP_H2M_NOTIF_Q_SZ_VER3) :
                cpu_to_le32(SAP_H2M_NOTIF_Q_SZ_VER4);
        m2h->q_ctrl_blk[SAP_QUEUE_IDX_DATA].size =
                cpu_to_le32(SAP_M2H_DATA_Q_SZ);
        m2h->q_ctrl_blk[SAP_QUEUE_IDX_NOTIF].size =
                cpu_to_le32(SAP_M2H_NOTIF_Q_SZ);

        /* q_head points to the start of the first queue */
        q_head = (void *)(mem->ctrl + 1);

        /* Initialize the queue heads */
        for (dir = 0; dir < SAP_DIRECTION_MAX; dir++) {
                for (queue = 0; queue < SAP_QUEUE_IDX_MAX; queue++) {
                        mem->q_head[dir][queue] = q_head;
                        q_head +=
                                le32_to_cpu(mem->ctrl->dir[dir].q_ctrl_blk[queue].size);
                        mem->q_size[dir][queue] =
                                le32_to_cpu(mem->ctrl->dir[dir].q_ctrl_blk[queue].size);
                }
        }

        *(__le32 *)q_head = cpu_to_le32(SAP_CONTROL_BLOCK_ID);
}

static ssize_t iwl_mei_write_cyclic_buf(struct mei_cl_device *cldev,
                                        struct iwl_sap_q_ctrl_blk *notif_q,
                                        u8 *q_head,
                                        const struct iwl_sap_hdr *hdr,
                                        u32 q_sz)
{
        u32 rd = le32_to_cpu(READ_ONCE(notif_q->rd_ptr));
        u32 wr = le32_to_cpu(READ_ONCE(notif_q->wr_ptr));
        size_t room_in_buf;
        size_t tx_sz = sizeof(*hdr) + le16_to_cpu(hdr->len);

        if (rd > q_sz || wr > q_sz) {
                dev_err(&cldev->dev,
                        "Pointers are past the end of the buffer\n");
                return -EINVAL;
        }

        room_in_buf = wr >= rd ? q_sz - wr + rd : rd - wr;

        /* we don't have enough room for the data to write */
        if (room_in_buf < tx_sz) {
                dev_err(&cldev->dev,
                        "Not enough room in the buffer\n");
                return -ENOSPC;
        }

        if (wr + tx_sz <= q_sz) {
                memcpy(q_head + wr, hdr, tx_sz);
        } else {
                memcpy(q_head + wr, hdr, q_sz - wr);
                memcpy(q_head, (const u8 *)hdr + q_sz - wr, tx_sz - (q_sz - wr));
        }

        WRITE_ONCE(notif_q->wr_ptr, cpu_to_le32((wr + tx_sz) % q_sz));
        return 0;
}

static bool iwl_mei_host_to_me_data_pending(const struct iwl_mei *mei)
{
        struct iwl_sap_q_ctrl_blk *notif_q;
        struct iwl_sap_dir *dir;

        dir = &mei->shared_mem.ctrl->dir[SAP_DIRECTION_HOST_TO_ME];
        notif_q = &dir->q_ctrl_blk[SAP_QUEUE_IDX_DATA];

        if (READ_ONCE(notif_q->wr_ptr) != READ_ONCE(notif_q->rd_ptr))
                return true;

        notif_q = &dir->q_ctrl_blk[SAP_QUEUE_IDX_NOTIF];
        return READ_ONCE(notif_q->wr_ptr) != READ_ONCE(notif_q->rd_ptr);
}

static int iwl_mei_send_check_shared_area(struct mei_cl_device *cldev)
{
        struct iwl_mei *mei = mei_cldev_get_drvdata(cldev);
        struct iwl_sap_me_msg_start msg = {
                .hdr.type = cpu_to_le32(SAP_ME_MSG_CHECK_SHARED_AREA),
                .hdr.seq_num = cpu_to_le32(atomic_inc_return(&mei->seq_no)),
        };
        int ret;

        lockdep_assert_held(&iwl_mei_mutex);

        if (mei->csa_throttled)
                return 0;

        trace_iwlmei_me_msg(&msg.hdr, true);
        ret = mei_cldev_send(cldev, (void *)&msg, sizeof(msg));
        if (ret != sizeof(msg)) {
                dev_err(&cldev->dev,
                        "failed to send the SAP_ME_MSG_CHECK_SHARED_AREA message %d\n",
                        ret);
                return ret;
        }

        mei->csa_throttled = true;

        schedule_delayed_work(&mei->csa_throttle_end_wk,
                              msecs_to_jiffies(100));

        return 0;
}

static void iwl_mei_csa_throttle_end_wk(struct work_struct *wk)
{
        struct iwl_mei *mei =
                container_of(wk, struct iwl_mei, csa_throttle_end_wk.work);

        mutex_lock(&iwl_mei_mutex);

        mei->csa_throttled = false;

        if (iwl_mei_host_to_me_data_pending(mei))
                iwl_mei_send_check_shared_area(mei->cldev);

        mutex_unlock(&iwl_mei_mutex);
}

static int iwl_mei_send_sap_msg_payload(struct mei_cl_device *cldev,
                                        struct iwl_sap_hdr *hdr)
{
        struct iwl_mei *mei = mei_cldev_get_drvdata(cldev);
        struct iwl_sap_q_ctrl_blk *notif_q;
        struct iwl_sap_dir *dir;
        void *q_head;
        u32 q_sz;
        int ret;

        lockdep_assert_held(&iwl_mei_mutex);

        if (!mei->shared_mem.ctrl) {
                dev_err(&cldev->dev,
                        "No shared memory, can't send any SAP message\n");
                return -EINVAL;
        }

        if (!iwl_mei_is_connected()) {
                dev_err(&cldev->dev,
                        "Can't send a SAP message if we're not connected\n");
                return -ENODEV;
        }

        hdr->seq_num = cpu_to_le32(atomic_inc_return(&mei->sap_seq_no));
        dev_dbg(&cldev->dev, "Sending %d\n", hdr->type);

        dir = &mei->shared_mem.ctrl->dir[SAP_DIRECTION_HOST_TO_ME];
        notif_q = &dir->q_ctrl_blk[SAP_QUEUE_IDX_NOTIF];
        q_head = mei->shared_mem.q_head[SAP_DIRECTION_HOST_TO_ME][SAP_QUEUE_IDX_NOTIF];
        q_sz = mei->shared_mem.q_size[SAP_DIRECTION_HOST_TO_ME][SAP_QUEUE_IDX_NOTIF];
        ret = iwl_mei_write_cyclic_buf(q_head, notif_q, q_head, hdr, q_sz);

        if (ret < 0)
                return ret;

        trace_iwlmei_sap_cmd(hdr, true);

        return iwl_mei_send_check_shared_area(cldev);
}

void iwl_mei_add_data_to_ring(struct sk_buff *skb, bool cb_tx)
{
        struct iwl_sap_q_ctrl_blk *notif_q;
        struct iwl_sap_dir *dir;
        struct iwl_mei *mei;
        size_t room_in_buf;
        size_t tx_sz;
        size_t hdr_sz;
        u32 q_sz;
        u32 rd;
        u32 wr;
        u8 *q_head;

        if (!iwl_mei_global_cldev)
                return;

        mei = mei_cldev_get_drvdata(iwl_mei_global_cldev);

        /*
         * We access this path for Rx packets (the more common case)
         * and from Tx path when we send DHCP packets, the latter is
         * very unlikely.
         * Take the lock already here to make sure we see that remove()
         * might have cleared the IWL_MEI_STATUS_SAP_CONNECTED bit.
         */
        spin_lock_bh(&mei->data_q_lock);

        if (!iwl_mei_is_connected()) {
                spin_unlock_bh(&mei->data_q_lock);
                return;
        }

        /*
         * We are in a RCU critical section and the remove from the CSME bus
         * which would free this memory waits for the readers to complete (this
         * is done in netdev_rx_handler_unregister).
         */
        dir = &mei->shared_mem.ctrl->dir[SAP_DIRECTION_HOST_TO_ME];
        notif_q = &dir->q_ctrl_blk[SAP_QUEUE_IDX_DATA];
        q_head = mei->shared_mem.q_head[SAP_DIRECTION_HOST_TO_ME][SAP_QUEUE_IDX_DATA];
        q_sz = mei->shared_mem.q_size[SAP_DIRECTION_HOST_TO_ME][SAP_QUEUE_IDX_DATA];

        rd = le32_to_cpu(READ_ONCE(notif_q->rd_ptr));
        wr = le32_to_cpu(READ_ONCE(notif_q->wr_ptr));
        hdr_sz = cb_tx ? sizeof(struct iwl_sap_cb_data) :
                         sizeof(struct iwl_sap_hdr);
        tx_sz = skb->len + hdr_sz;

        if (rd > q_sz || wr > q_sz) {
                dev_err(&mei->cldev->dev,
                        "can't write the data: pointers are past the end of the buffer\n");
                goto out;
        }

        room_in_buf = wr >= rd ? q_sz - wr + rd : rd - wr;

        /* we don't have enough room for the data to write */
        if (room_in_buf < tx_sz) {
                dev_err(&mei->cldev->dev,
                        "Not enough room in the buffer for this data\n");
                goto out;
        }

        if (skb_headroom(skb) < hdr_sz) {
                dev_err(&mei->cldev->dev,
                        "Not enough headroom in the skb to write the SAP header\n");
                goto out;
        }

        if (cb_tx) {
                struct iwl_sap_cb_data *cb_hdr = skb_push(skb, sizeof(*cb_hdr));

                memset(cb_hdr, 0, sizeof(*cb_hdr));
                cb_hdr->hdr.type = cpu_to_le16(SAP_MSG_CB_DATA_PACKET);
                cb_hdr->hdr.len = cpu_to_le16(skb->len - sizeof(cb_hdr->hdr));
                cb_hdr->hdr.seq_num = cpu_to_le32(atomic_inc_return(&mei->sap_seq_no));
                cb_hdr->to_me_filt_status = cpu_to_le32(BIT(CB_TX_DHCP_FILT_IDX));
                cb_hdr->data_len = cpu_to_le32(skb->len - sizeof(*cb_hdr));
                trace_iwlmei_sap_data(skb, IWL_SAP_TX_DHCP);
        } else {
                struct iwl_sap_hdr *hdr = skb_push(skb, sizeof(*hdr));

                hdr->type = cpu_to_le16(SAP_MSG_DATA_PACKET);
                hdr->len = cpu_to_le16(skb->len - sizeof(*hdr));
                hdr->seq_num = cpu_to_le32(atomic_inc_return(&mei->sap_seq_no));
                trace_iwlmei_sap_data(skb, IWL_SAP_TX_DATA_FROM_AIR);
        }

        if (wr + tx_sz <= q_sz) {
                skb_copy_bits(skb, 0, q_head + wr, tx_sz);
        } else {
                skb_copy_bits(skb, 0, q_head + wr, q_sz - wr);
                skb_copy_bits(skb, q_sz - wr, q_head, tx_sz - (q_sz - wr));
        }

        WRITE_ONCE(notif_q->wr_ptr, cpu_to_le32((wr + tx_sz) % q_sz));

out:
        spin_unlock_bh(&mei->data_q_lock);
}

static int
iwl_mei_send_sap_msg(struct mei_cl_device *cldev, u16 type)
{
        struct iwl_sap_hdr msg = {
                .type = cpu_to_le16(type),
        };

        return iwl_mei_send_sap_msg_payload(cldev, &msg);
}

static void iwl_mei_send_csa_msg_wk(struct work_struct *wk)
{
        struct iwl_mei *mei =
                container_of(wk, struct iwl_mei, send_csa_msg_wk);

        if (!iwl_mei_is_connected())
                return;

        mutex_lock(&iwl_mei_mutex);

        iwl_mei_send_check_shared_area(mei->cldev);

        mutex_unlock(&iwl_mei_mutex);
}

/* Called in a RCU read critical section from netif_receive_skb */
static rx_handler_result_t iwl_mei_rx_handler(struct sk_buff **pskb)
{
        struct sk_buff *skb = *pskb;
        struct iwl_mei *mei =
                rcu_dereference(skb->dev->rx_handler_data);
        struct iwl_mei_filters *filters = rcu_dereference(mei->filters);
        bool rx_for_csme = false;
        rx_handler_result_t res;

        /*
         * remove() unregisters this handler and synchronize_net, so this
         * should never happen.
         */
        if (!iwl_mei_is_connected()) {
                dev_err(&mei->cldev->dev,
                        "Got an Rx packet, but we're not connected to SAP?\n");
                return RX_HANDLER_PASS;
        }

        if (filters)
                res = iwl_mei_rx_filter(skb, &filters->filters, &rx_for_csme);
        else
                res = RX_HANDLER_PASS;

        /*
         * The data is already on the ring of the shared area, all we
         * need to do is to tell the CSME firmware to check what we have
         * there.
         */
        if (rx_for_csme)
                schedule_work(&mei->send_csa_msg_wk);

        if (res != RX_HANDLER_PASS) {
                trace_iwlmei_sap_data(skb, IWL_SAP_RX_DATA_DROPPED_FROM_AIR);
                dev_kfree_skb(skb);
        }

        return res;
}

static void iwl_mei_netdev_work(struct work_struct *wk)
{
        struct iwl_mei *mei =
                container_of(wk, struct iwl_mei, netdev_work);
        struct net_device *netdev;

        /*
         * First take rtnl and only then the mutex to avoid an ABBA
         * with iwl_mei_set_netdev()
         */
        rtnl_lock();
        mutex_lock(&iwl_mei_mutex);

        netdev = rcu_dereference_protected(iwl_mei_cache.netdev,
                                           lockdep_is_held(&iwl_mei_mutex));
        if (netdev) {
                if (mei->amt_enabled)
                        netdev_rx_handler_register(netdev, iwl_mei_rx_handler,
                                                   mei);
                else
                        netdev_rx_handler_unregister(netdev);
        }

        mutex_unlock(&iwl_mei_mutex);
        rtnl_unlock();
}

static void
iwl_mei_handle_rx_start_ok(struct mei_cl_device *cldev,
                           const struct iwl_sap_me_msg_start_ok *rsp,
                           ssize_t len)
{
        if (len != sizeof(*rsp)) {
                dev_err(&cldev->dev,
                        "got invalid SAP_ME_MSG_START_OK from CSME firmware\n");
                dev_err(&cldev->dev,
                        "size is incorrect: %zd instead of %zu\n",
                        len, sizeof(*rsp));
                return;
        }

        if (rsp->supported_version != iwl_mei_cache.sap_version) {
                dev_err(&cldev->dev,
                        "didn't get the expected version: got %d\n",
                        rsp->supported_version);
                return;
        }

        mutex_lock(&iwl_mei_mutex);
        set_bit(IWL_MEI_STATUS_SAP_CONNECTED, &iwl_mei_status);
        /*
         * We'll receive AMT_STATE SAP message in a bit and
         * that will continue the flow
         */
        mutex_unlock(&iwl_mei_mutex);
}

static void iwl_mei_handle_csme_filters(struct mei_cl_device *cldev,
                                        const struct iwl_sap_csme_filters *filters)
{
        struct iwl_mei *mei = mei_cldev_get_drvdata(iwl_mei_global_cldev);
        struct iwl_mei_filters *new_filters;
        struct iwl_mei_filters *old_filters;

        old_filters =
                rcu_dereference_protected(mei->filters,
                                          lockdep_is_held(&iwl_mei_mutex));

        new_filters = kzalloc_obj(*new_filters);
        if (!new_filters)
                return;

        /* Copy the OOB filters */
        new_filters->filters = filters->filters;

        rcu_assign_pointer(mei->filters, new_filters);

        if (old_filters)
                kfree_rcu(old_filters, rcu_head);
}

static void
iwl_mei_handle_conn_status(struct mei_cl_device *cldev,
                           const struct iwl_sap_notif_conn_status *status)
{
        struct iwl_mei *mei = mei_cldev_get_drvdata(cldev);
        struct iwl_mei_conn_info conn_info = {
                .lp_state = le32_to_cpu(status->link_prot_state),
                .ssid_len = le32_to_cpu(status->conn_info.ssid_len),
                .channel = status->conn_info.channel,
                .band = status->conn_info.band,
                .auth_mode = le32_to_cpu(status->conn_info.auth_mode),
                .pairwise_cipher = le32_to_cpu(status->conn_info.pairwise_cipher),
        };

        if (!iwl_mei_cache.ops ||
            conn_info.ssid_len > ARRAY_SIZE(conn_info.ssid))
                return;

        memcpy(conn_info.ssid, status->conn_info.ssid, conn_info.ssid_len);
        ether_addr_copy(conn_info.bssid, status->conn_info.bssid);

        iwl_mei_cache.ops->me_conn_status(iwl_mei_cache.priv, &conn_info);

        mei->link_prot_state = status->link_prot_state;

        /*
         * Update the Rfkill state in case the host does not own the device:
         * if we are in Link Protection, ask to not touch the device, else,
         * unblock rfkill.
         * If the host owns the device, inform the user space whether it can
         * roam.
         */
        if (mei->got_ownership)
                iwl_mei_cache.ops->roaming_forbidden(iwl_mei_cache.priv,
                                                     status->link_prot_state);
        else
                iwl_mei_cache.ops->rfkill(iwl_mei_cache.priv,
                                          status->link_prot_state, false);
}

static void iwl_mei_set_init_conf(struct iwl_mei *mei)
{
        struct iwl_sap_notif_host_link_up link_msg = {
                .hdr.type = cpu_to_le16(SAP_MSG_NOTIF_HOST_LINK_UP),
                .hdr.len = cpu_to_le16(sizeof(link_msg) - sizeof(link_msg.hdr)),
        };
        struct iwl_sap_notif_country_code mcc_msg = {
                .hdr.type = cpu_to_le16(SAP_MSG_NOTIF_COUNTRY_CODE),
                .hdr.len = cpu_to_le16(sizeof(mcc_msg) - sizeof(mcc_msg.hdr)),
                .mcc = cpu_to_le16(iwl_mei_cache.mcc),
        };
        struct iwl_sap_notif_sar_limits sar_msg = {
                .hdr.type = cpu_to_le16(SAP_MSG_NOTIF_SAR_LIMITS),
                .hdr.len = cpu_to_le16(sizeof(sar_msg) - sizeof(sar_msg.hdr)),
        };
        struct iwl_sap_notif_host_nic_info nic_info_msg = {
                .hdr.type = cpu_to_le16(SAP_MSG_NOTIF_NIC_INFO),
                .hdr.len = cpu_to_le16(sizeof(nic_info_msg) - sizeof(nic_info_msg.hdr)),
        };
        struct iwl_sap_msg_dw rfkill_msg = {
                .hdr.type = cpu_to_le16(SAP_MSG_NOTIF_RADIO_STATE),
                .hdr.len = cpu_to_le16(sizeof(rfkill_msg) - sizeof(rfkill_msg.hdr)),
                .val = cpu_to_le32(iwl_mei_cache.rf_kill),
        };

        /* wifi driver has registered already */
        if (iwl_mei_cache.ops) {
                iwl_mei_send_sap_msg(mei->cldev,
                                     SAP_MSG_NOTIF_WIFIDR_UP);
                iwl_mei_cache.ops->sap_connected(iwl_mei_cache.priv);
        }

        iwl_mei_send_sap_msg(mei->cldev, SAP_MSG_NOTIF_WHO_OWNS_NIC);

        if (iwl_mei_cache.conn_info) {
                link_msg.conn_info = *iwl_mei_cache.conn_info;
                iwl_mei_send_sap_msg_payload(mei->cldev, &link_msg.hdr);
        }

        iwl_mei_send_sap_msg_payload(mei->cldev, &mcc_msg.hdr);

        if (iwl_mei_cache.power_limit) {
                memcpy(sar_msg.sar_chain_info_table, iwl_mei_cache.power_limit,
                       sizeof(sar_msg.sar_chain_info_table));
                iwl_mei_send_sap_msg_payload(mei->cldev, &sar_msg.hdr);
        }

        if (is_valid_ether_addr(iwl_mei_cache.mac_address)) {
                ether_addr_copy(nic_info_msg.mac_address,
                                iwl_mei_cache.mac_address);
                ether_addr_copy(nic_info_msg.nvm_address,
                                iwl_mei_cache.nvm_address);
                iwl_mei_send_sap_msg_payload(mei->cldev, &nic_info_msg.hdr);
        }

        iwl_mei_send_sap_msg_payload(mei->cldev, &rfkill_msg.hdr);
}

static void iwl_mei_handle_amt_state(struct mei_cl_device *cldev,
                                     const struct iwl_sap_msg_dw *dw)
{
        struct iwl_mei *mei = mei_cldev_get_drvdata(cldev);

        mutex_lock(&iwl_mei_mutex);

        if (mei->amt_enabled == !!le32_to_cpu(dw->val))
                goto out;

        mei->amt_enabled = dw->val;

        if (mei->amt_enabled)
                iwl_mei_set_init_conf(mei);
        else if (iwl_mei_cache.ops)
                iwl_mei_cache.ops->rfkill(iwl_mei_cache.priv, false, false);

        schedule_work(&mei->netdev_work);

out:
        mutex_unlock(&iwl_mei_mutex);
}

static void iwl_mei_handle_nic_owner(struct mei_cl_device *cldev,
                                     const struct iwl_sap_msg_dw *dw)
{
        struct iwl_mei *mei = mei_cldev_get_drvdata(cldev);

        mei->got_ownership = dw->val != cpu_to_le32(SAP_NIC_OWNER_ME);
}

static void iwl_mei_handle_can_release_ownership(struct mei_cl_device *cldev,
                                                 const void *payload)
{
        /* We can get ownership and driver is registered, go ahead */
        if (iwl_mei_cache.ops)
                iwl_mei_send_sap_msg(cldev,
                                     SAP_MSG_NOTIF_HOST_ASKS_FOR_NIC_OWNERSHIP);
}

static void iwl_mei_handle_csme_taking_ownership(struct mei_cl_device *cldev,
                                                 const void *payload)
{
        struct iwl_mei *mei = mei_cldev_get_drvdata(cldev);

        dev_info(&cldev->dev, "CSME takes ownership\n");

        mei->got_ownership = false;

        if (iwl_mei_cache.ops && !mei->device_down) {
                /*
                 * Remember to send CSME_OWNERSHIP_CONFIRMED when the wifi
                 * driver is finished taking the device down.
                 */
                mei->csme_taking_ownership = true;

                iwl_mei_cache.ops->rfkill(iwl_mei_cache.priv, true, true);
        } else {
                iwl_mei_send_sap_msg(cldev,
                                     SAP_MSG_NOTIF_CSME_OWNERSHIP_CONFIRMED);
                schedule_delayed_work(&mei->ownership_dwork,
                                      MEI_OWNERSHIP_RETAKE_TIMEOUT_MS);
        }
}

static void iwl_mei_handle_nvm(struct mei_cl_device *cldev,
                               const struct iwl_sap_nvm *sap_nvm)
{
        struct iwl_mei *mei = mei_cldev_get_drvdata(cldev);
        const struct iwl_mei_nvm *mei_nvm = (const void *)sap_nvm;
        int i;

        kfree(mei->nvm);
        mei->nvm = kzalloc_obj(*mei_nvm);
        if (!mei->nvm)
                return;

        ether_addr_copy(mei->nvm->hw_addr, sap_nvm->hw_addr);
        mei->nvm->n_hw_addrs = sap_nvm->n_hw_addrs;
        mei->nvm->radio_cfg = le32_to_cpu(sap_nvm->radio_cfg);
        mei->nvm->caps = le32_to_cpu(sap_nvm->caps);
        mei->nvm->nvm_version = le32_to_cpu(sap_nvm->nvm_version);

        for (i = 0; i < ARRAY_SIZE(mei->nvm->channels); i++)
                mei->nvm->channels[i] = le32_to_cpu(sap_nvm->channels[i]);

        wake_up_all(&mei->get_nvm_wq);
}

static void iwl_mei_handle_rx_host_own_req(struct mei_cl_device *cldev,
                                           const struct iwl_sap_msg_dw *dw)
{
        struct iwl_mei *mei = mei_cldev_get_drvdata(cldev);

        /*
         * This means that we can't use the wifi device right now, CSME is not
         * ready to let us use it.
         */
        if (!dw->val) {
                dev_info(&cldev->dev, "Ownership req denied\n");
                return;
        }

        mei->got_ownership = true;
        wake_up_all(&mei->get_ownership_wq);

        iwl_mei_send_sap_msg(cldev,
                             SAP_MSG_NOTIF_HOST_OWNERSHIP_CONFIRMED);

        /* We can now start the connection, unblock rfkill */
        if (iwl_mei_cache.ops)
                iwl_mei_cache.ops->rfkill(iwl_mei_cache.priv, false, false);
}

static void iwl_mei_handle_pldr_ack(struct mei_cl_device *cldev,
                                    const struct iwl_sap_pldr_ack_data *ack)
{
        struct iwl_mei *mei = mei_cldev_get_drvdata(cldev);

        mei->pldr_active = le32_to_cpu(ack->status) == SAP_PLDR_STATUS_SUCCESS;
        wake_up_all(&mei->pldr_wq);
}

static void iwl_mei_handle_ping(struct mei_cl_device *cldev,
                                const struct iwl_sap_hdr *hdr)
{
        iwl_mei_send_sap_msg(cldev, SAP_MSG_NOTIF_PONG);
}

static void iwl_mei_handle_sap_msg(struct mei_cl_device *cldev,
                                   const struct iwl_sap_hdr *hdr)
{
        u16 len = le16_to_cpu(hdr->len) + sizeof(*hdr);
        u16 type = le16_to_cpu(hdr->type);

        dev_dbg(&cldev->dev,
                "Got a new SAP message: type %d, len %d, seq %d\n",
                le16_to_cpu(hdr->type), len,
                le32_to_cpu(hdr->seq_num));

#define SAP_MSG_HANDLER(_cmd, _handler, _sz)                            \
        case SAP_MSG_NOTIF_ ## _cmd:                                    \
                if (len < _sz) {                                        \
                        dev_err(&cldev->dev,                            \
                                "Bad size for %d: %u < %u\n",           \
                                le16_to_cpu(hdr->type),                 \
                                (unsigned int)len,                      \
                                (unsigned int)_sz);                     \
                        break;                                          \
                }                                                       \
                mutex_lock(&iwl_mei_mutex);                             \
                _handler(cldev, (const void *)hdr);                     \
                mutex_unlock(&iwl_mei_mutex);                           \
                break

#define SAP_MSG_HANDLER_NO_LOCK(_cmd, _handler, _sz)                    \
        case SAP_MSG_NOTIF_ ## _cmd:                                    \
                if (len < _sz) {                                        \
                        dev_err(&cldev->dev,                            \
                                "Bad size for %d: %u < %u\n",           \
                                le16_to_cpu(hdr->type),                 \
                                (unsigned int)len,                      \
                                (unsigned int)_sz);                     \
                        break;                                          \
                }                                                       \
                _handler(cldev, (const void *)hdr);                     \
                break

#define SAP_MSG_HANDLER_NO_HANDLER(_cmd, _sz)                           \
        case SAP_MSG_NOTIF_ ## _cmd:                                    \
                if (len < _sz) {                                        \
                        dev_err(&cldev->dev,                            \
                                "Bad size for %d: %u < %u\n",           \
                                le16_to_cpu(hdr->type),                 \
                                (unsigned int)len,                      \
                                (unsigned int)_sz);                     \
                        break;                                          \
                }                                                       \
                break

        switch (type) {
        SAP_MSG_HANDLER(PING, iwl_mei_handle_ping, 0);
        SAP_MSG_HANDLER(CSME_FILTERS,
                        iwl_mei_handle_csme_filters,
                        sizeof(struct iwl_sap_csme_filters));
        SAP_MSG_HANDLER(CSME_CONN_STATUS,
                        iwl_mei_handle_conn_status,
                        sizeof(struct iwl_sap_notif_conn_status));
        SAP_MSG_HANDLER_NO_LOCK(AMT_STATE,
                                iwl_mei_handle_amt_state,
                                sizeof(struct iwl_sap_msg_dw));
        SAP_MSG_HANDLER_NO_HANDLER(PONG, 0);
        SAP_MSG_HANDLER(NVM, iwl_mei_handle_nvm,
                        sizeof(struct iwl_sap_nvm));
        SAP_MSG_HANDLER(CSME_REPLY_TO_HOST_OWNERSHIP_REQ,
                        iwl_mei_handle_rx_host_own_req,
                        sizeof(struct iwl_sap_msg_dw));
        SAP_MSG_HANDLER(NIC_OWNER, iwl_mei_handle_nic_owner,
                        sizeof(struct iwl_sap_msg_dw));
        SAP_MSG_HANDLER(CSME_CAN_RELEASE_OWNERSHIP,
                        iwl_mei_handle_can_release_ownership, 0);
        SAP_MSG_HANDLER(CSME_TAKING_OWNERSHIP,
                        iwl_mei_handle_csme_taking_ownership, 0);
        SAP_MSG_HANDLER(PLDR_ACK, iwl_mei_handle_pldr_ack,
                        sizeof(struct iwl_sap_pldr_ack_data));
        default:
        /*
         * This is not really an error, there are message that we decided
         * to ignore, yet, it is useful to be able to leave a note if debug
         * is enabled.
         */
        dev_dbg(&cldev->dev, "Unsupported message: type %d, len %d\n",
                le16_to_cpu(hdr->type), len);
        }

#undef SAP_MSG_HANDLER
#undef SAP_MSG_HANDLER_NO_LOCK
}

static void iwl_mei_read_from_q(const u8 *q_head, u32 q_sz,
                                u32 *_rd, u32 wr,
                                void *_buf, u32 len)
{
        u8 *buf = _buf;
        u32 rd = *_rd;

        if (rd + len <= q_sz) {
                memcpy(buf, q_head + rd, len);
                rd += len;
        } else {
                memcpy(buf, q_head + rd, q_sz - rd);
                memcpy(buf + q_sz - rd, q_head, len - (q_sz - rd));
                rd = len - (q_sz - rd);
        }

        *_rd = rd;
}

#define QOS_HDR_IV_SNAP_LEN (sizeof(struct ieee80211_qos_hdr) +      \
                             IEEE80211_TKIP_IV_LEN +                 \
                             sizeof(rfc1042_header) + ETH_TLEN)

static void iwl_mei_handle_sap_data(struct mei_cl_device *cldev,
                                    const u8 *q_head, u32 q_sz,
                                    u32 rd, u32 wr, ssize_t valid_rx_sz,
                                    struct sk_buff_head *tx_skbs)
{
        struct iwl_sap_hdr hdr;
        struct net_device *netdev =
                rcu_dereference_protected(iwl_mei_cache.netdev,
                                          lockdep_is_held(&iwl_mei_mutex));

        if (!netdev)
                return;

        while (valid_rx_sz >= sizeof(hdr)) {
                struct ethhdr *ethhdr;
                unsigned char *data;
                struct sk_buff *skb;
                u16 len;

                iwl_mei_read_from_q(q_head, q_sz, &rd, wr, &hdr, sizeof(hdr));
                valid_rx_sz -= sizeof(hdr);
                len = le16_to_cpu(hdr.len);

                if (valid_rx_sz < len) {
                        dev_err(&cldev->dev,
                                "Data queue is corrupted: valid data len %zd, len %d\n",
                                valid_rx_sz, len);
                        break;
                }

                if (len < sizeof(*ethhdr)) {
                        dev_err(&cldev->dev,
                                "Data len is smaller than an ethernet header? len = %d\n",
                                len);
                }

                valid_rx_sz -= len;

                if (le16_to_cpu(hdr.type) != SAP_MSG_DATA_PACKET) {
                        dev_err(&cldev->dev, "Unsupported Rx data: type %d, len %d\n",
                                le16_to_cpu(hdr.type), len);
                        continue;
                }

                /* We need enough room for the WiFi header + SNAP + IV */
                skb = netdev_alloc_skb(netdev, len + QOS_HDR_IV_SNAP_LEN);
                if (!skb)
                        continue;

                skb_reserve(skb, QOS_HDR_IV_SNAP_LEN);
                ethhdr = skb_push(skb, sizeof(*ethhdr));

                iwl_mei_read_from_q(q_head, q_sz, &rd, wr,
                                    ethhdr, sizeof(*ethhdr));
                len -= sizeof(*ethhdr);

                skb_reset_mac_header(skb);
                skb_reset_network_header(skb);
                skb->protocol = ethhdr->h_proto;

                data = skb_put(skb, len);
                iwl_mei_read_from_q(q_head, q_sz, &rd, wr, data, len);

                /*
                 * Enqueue the skb here so that it can be sent later when we
                 * do not hold the mutex. TX'ing a packet with a mutex held is
                 * possible, but it wouldn't be nice to forbid the TX path to
                 * call any of iwlmei's functions, since every API from iwlmei
                 * needs the mutex.
                 */
                __skb_queue_tail(tx_skbs, skb);
        }
}

static void iwl_mei_handle_sap_rx_cmd(struct mei_cl_device *cldev,
                                      const u8 *q_head, u32 q_sz,
                                      u32 rd, u32 wr, ssize_t valid_rx_sz)
{
        struct page *p = alloc_page(GFP_KERNEL);
        struct iwl_sap_hdr *hdr;

        if (!p)
                return;

        hdr = page_address(p);

        while (valid_rx_sz >= sizeof(*hdr)) {
                u16 len;

                iwl_mei_read_from_q(q_head, q_sz, &rd, wr, hdr, sizeof(*hdr));
                valid_rx_sz -= sizeof(*hdr);
                len = le16_to_cpu(hdr->len);

                if (valid_rx_sz < len)
                        break;

                iwl_mei_read_from_q(q_head, q_sz, &rd, wr, hdr + 1, len);

                trace_iwlmei_sap_cmd(hdr, false);
                iwl_mei_handle_sap_msg(cldev, hdr);
                valid_rx_sz -= len;
        }

        /* valid_rx_sz must be 0 now... */
        if (valid_rx_sz)
                dev_err(&cldev->dev,
                        "More data in the buffer although we read it all\n");

        __free_page(p);
}

static void iwl_mei_handle_sap_rx(struct mei_cl_device *cldev,
                                  struct iwl_sap_q_ctrl_blk *notif_q,
                                  const u8 *q_head,
                                  struct sk_buff_head *skbs,
                                  u32 q_sz)
{
        u32 rd = le32_to_cpu(READ_ONCE(notif_q->rd_ptr));
        u32 wr = le32_to_cpu(READ_ONCE(notif_q->wr_ptr));
        ssize_t valid_rx_sz;

        if (rd > q_sz || wr > q_sz) {
                dev_err(&cldev->dev,
                        "Pointers are past the buffer limit\n");
                return;
        }

        if (rd == wr)
                return;

        valid_rx_sz = wr > rd ? wr - rd : q_sz - rd + wr;

        if (skbs)
                iwl_mei_handle_sap_data(cldev, q_head, q_sz, rd, wr,
                                        valid_rx_sz, skbs);
        else
                iwl_mei_handle_sap_rx_cmd(cldev, q_head, q_sz, rd, wr,
                                          valid_rx_sz);

        /* Increment the read pointer to point to the write pointer */
        WRITE_ONCE(notif_q->rd_ptr, cpu_to_le32(wr));
}

static void iwl_mei_handle_check_shared_area(struct mei_cl_device *cldev)
{
        struct iwl_mei *mei = mei_cldev_get_drvdata(cldev);
        struct iwl_sap_q_ctrl_blk *notif_q;
        struct sk_buff_head tx_skbs;
        struct iwl_sap_dir *dir;
        void *q_head;
        u32 q_sz;

        if (!mei->shared_mem.ctrl)
                return;

        dir = &mei->shared_mem.ctrl->dir[SAP_DIRECTION_ME_TO_HOST];
        notif_q = &dir->q_ctrl_blk[SAP_QUEUE_IDX_NOTIF];
        q_head = mei->shared_mem.q_head[SAP_DIRECTION_ME_TO_HOST][SAP_QUEUE_IDX_NOTIF];
        q_sz = mei->shared_mem.q_size[SAP_DIRECTION_ME_TO_HOST][SAP_QUEUE_IDX_NOTIF];

        /*
         * Do not hold the mutex here, but rather each and every message
         * handler takes it.
         * This allows message handlers to take it at a certain time.
         */
        iwl_mei_handle_sap_rx(cldev, notif_q, q_head, NULL, q_sz);

        mutex_lock(&iwl_mei_mutex);
        dir = &mei->shared_mem.ctrl->dir[SAP_DIRECTION_ME_TO_HOST];
        notif_q = &dir->q_ctrl_blk[SAP_QUEUE_IDX_DATA];
        q_head = mei->shared_mem.q_head[SAP_DIRECTION_ME_TO_HOST][SAP_QUEUE_IDX_DATA];
        q_sz = mei->shared_mem.q_size[SAP_DIRECTION_ME_TO_HOST][SAP_QUEUE_IDX_DATA];

        __skb_queue_head_init(&tx_skbs);

        iwl_mei_handle_sap_rx(cldev, notif_q, q_head, &tx_skbs, q_sz);

        if (skb_queue_empty(&tx_skbs)) {
                mutex_unlock(&iwl_mei_mutex);
                return;
        }

        /*
         * Take the RCU read lock before we unlock the mutex to make sure that
         * even if the netdev is replaced by another non-NULL netdev right after
         * we unlock the mutex, the old netdev will still be valid when we
         * transmit the frames. We can't allow to replace the netdev here because
         * the skbs hold a pointer to the netdev.
         */
        rcu_read_lock();

        mutex_unlock(&iwl_mei_mutex);

        if (!rcu_access_pointer(iwl_mei_cache.netdev)) {
                dev_err(&cldev->dev, "Can't Tx without a netdev\n");
                skb_queue_purge(&tx_skbs);
                goto out;
        }

        while (!skb_queue_empty(&tx_skbs)) {
                struct sk_buff *skb = __skb_dequeue(&tx_skbs);

                trace_iwlmei_sap_data(skb, IWL_SAP_RX_DATA_TO_AIR);
                dev_queue_xmit(skb);
        }

out:
        rcu_read_unlock();
}

static void iwl_mei_rx(struct mei_cl_device *cldev)
{
        struct iwl_sap_me_msg_hdr *hdr;
        u8 msg[100];
        ssize_t ret;

        ret = mei_cldev_recv(cldev, (u8 *)&msg, sizeof(msg));
        if (ret < 0) {
                dev_err(&cldev->dev, "failed to receive data: %zd\n", ret);
                return;
        }

        if (ret == 0) {
                dev_err(&cldev->dev, "got an empty response\n");
                return;
        }

        hdr = (void *)msg;
        trace_iwlmei_me_msg(hdr, false);

        switch (le32_to_cpu(hdr->type)) {
        case SAP_ME_MSG_START_OK:
                BUILD_BUG_ON(sizeof(struct iwl_sap_me_msg_start_ok) >
                             sizeof(msg));

                iwl_mei_handle_rx_start_ok(cldev, (void *)msg, ret);
                break;
        case SAP_ME_MSG_CHECK_SHARED_AREA:
                iwl_mei_handle_check_shared_area(cldev);
                break;
        default:
                dev_err(&cldev->dev, "got a RX notification: %d\n",
                        le32_to_cpu(hdr->type));
                break;
        }
}

static int iwl_mei_send_start(struct mei_cl_device *cldev)
{
        struct iwl_mei *mei = mei_cldev_get_drvdata(cldev);
        struct iwl_sap_me_msg_start msg = {
                .hdr.type = cpu_to_le32(SAP_ME_MSG_START),
                .hdr.seq_num = cpu_to_le32(atomic_inc_return(&mei->seq_no)),
                .hdr.len = cpu_to_le32(sizeof(msg)),
                .supported_versions[0] = iwl_mei_cache.sap_version,
                .init_data_seq_num = cpu_to_le16(0x100),
                .init_notif_seq_num = cpu_to_le16(0x800),
        };
        int ret;

        trace_iwlmei_me_msg(&msg.hdr, true);
        ret = mei_cldev_send(cldev, (void *)&msg, sizeof(msg));
        if (ret != sizeof(msg)) {
                dev_err(&cldev->dev,
                        "failed to send the SAP_ME_MSG_START message %d\n",
                        ret);
                return ret;
        }

        return 0;
}

static int iwl_mei_enable(struct mei_cl_device *cldev)
{
        int ret;

        ret = mei_cldev_enable(cldev);
        if (ret < 0) {
                dev_err(&cldev->dev, "failed to enable the device: %d\n", ret);
                return ret;
        }

        ret = mei_cldev_register_rx_cb(cldev, iwl_mei_rx);
        if (ret) {
                dev_err(&cldev->dev,
                        "failed to register to the rx cb: %d\n", ret);
                mei_cldev_disable(cldev);
                return ret;
        }

        return 0;
}

struct iwl_mei_nvm *iwl_mei_get_nvm(void)
{
        struct iwl_mei_nvm *nvm = NULL;
        struct iwl_mei *mei;
        int ret;

        mutex_lock(&iwl_mei_mutex);

        if (!iwl_mei_is_connected())
                goto out;

        mei = mei_cldev_get_drvdata(iwl_mei_global_cldev);

        if (!mei)
                goto out;

        ret = iwl_mei_send_sap_msg(iwl_mei_global_cldev,
                                   SAP_MSG_NOTIF_GET_NVM);
        if (ret)
                goto out;

        mutex_unlock(&iwl_mei_mutex);

        ret = wait_event_timeout(mei->get_nvm_wq, mei->nvm, 2 * HZ);
        if (!ret)
                return NULL;

        mutex_lock(&iwl_mei_mutex);

        if (!iwl_mei_is_connected())
                goto out;

        mei = mei_cldev_get_drvdata(iwl_mei_global_cldev);

        if (!mei)
                goto out;

        if (mei->nvm)
                nvm = kmemdup(mei->nvm, sizeof(*mei->nvm), GFP_KERNEL);

out:
        mutex_unlock(&iwl_mei_mutex);
        return nvm;
}
EXPORT_SYMBOL_GPL(iwl_mei_get_nvm);

#define IWL_MEI_PLDR_NUM_RETRIES        3

int iwl_mei_pldr_req(void)
{
        struct iwl_mei *mei;
        int ret;
        struct iwl_sap_pldr_data msg = {
                .hdr.type = cpu_to_le16(SAP_MSG_NOTIF_PLDR),
                .hdr.len = cpu_to_le16(sizeof(msg) - sizeof(msg.hdr)),
        };
        int i;

        mutex_lock(&iwl_mei_mutex);

        /* In case we didn't have a bind */
        if (!iwl_mei_is_connected()) {
                ret = 0;
                goto out;
        }

        mei = mei_cldev_get_drvdata(iwl_mei_global_cldev);

        if (!mei) {
                ret = -ENODEV;
                goto out;
        }

        if (!mei->amt_enabled) {
                ret = 0;
                goto out;
        }

        for (i = 0; i < IWL_MEI_PLDR_NUM_RETRIES; i++) {
                ret = iwl_mei_send_sap_msg_payload(mei->cldev, &msg.hdr);
                mutex_unlock(&iwl_mei_mutex);
                if (ret)
                        return ret;

                ret = wait_event_timeout(mei->pldr_wq, mei->pldr_active, HZ / 2);
                if (ret)
                        break;

                /* Take the mutex for the next iteration */
                mutex_lock(&iwl_mei_mutex);
        }

        if (ret)
                return 0;

        ret = -ETIMEDOUT;
out:
        mutex_unlock(&iwl_mei_mutex);
        return ret;
}
EXPORT_SYMBOL_GPL(iwl_mei_pldr_req);

int iwl_mei_get_ownership(void)
{
        struct iwl_mei *mei;
        int ret;

        mutex_lock(&iwl_mei_mutex);

        /* In case we didn't have a bind */
        if (!iwl_mei_is_connected()) {
                ret = 0;
                goto out;
        }

        mei = mei_cldev_get_drvdata(iwl_mei_global_cldev);

        if (!mei) {
                ret = -ENODEV;
                goto out;
        }

        if (!mei->amt_enabled) {
                ret = 0;
                goto out;
        }

        if (mei->got_ownership) {
                ret = 0;
                goto out;
        }

        ret = iwl_mei_send_sap_msg(mei->cldev,
                                   SAP_MSG_NOTIF_HOST_ASKS_FOR_NIC_OWNERSHIP);
        if (ret)
                goto out;

        mutex_unlock(&iwl_mei_mutex);

        ret = wait_event_timeout(mei->get_ownership_wq,
                                 mei->got_ownership, HZ / 2);
        if (!ret) {
                schedule_delayed_work(&mei->ownership_dwork,
                                      MEI_OWNERSHIP_RETAKE_TIMEOUT_MS);
                return -ETIMEDOUT;
        }

        return 0;
out:
        mutex_unlock(&iwl_mei_mutex);
        return ret;
}
EXPORT_SYMBOL_GPL(iwl_mei_get_ownership);

void iwl_mei_alive_notif(bool success)
{
        struct iwl_mei *mei;
        struct iwl_sap_pldr_end_data msg = {
                .hdr.type = cpu_to_le16(SAP_MSG_NOTIF_PLDR_END),
                .hdr.len = cpu_to_le16(sizeof(msg) - sizeof(msg.hdr)),
                .status = success ? cpu_to_le32(SAP_PLDR_STATUS_SUCCESS) :
                        cpu_to_le32(SAP_PLDR_STATUS_FAILURE),
        };

        mutex_lock(&iwl_mei_mutex);

        if (!iwl_mei_is_connected())
                goto out;

        mei = mei_cldev_get_drvdata(iwl_mei_global_cldev);
        if (!mei || !mei->pldr_active)
                goto out;

        mei->pldr_active = false;

        iwl_mei_send_sap_msg_payload(mei->cldev, &msg.hdr);
out:
        mutex_unlock(&iwl_mei_mutex);
}
EXPORT_SYMBOL_GPL(iwl_mei_alive_notif);

void iwl_mei_host_associated(const struct iwl_mei_conn_info *conn_info,
                             const struct iwl_mei_colloc_info *colloc_info)
{
        struct iwl_sap_notif_host_link_up msg = {
                .hdr.type = cpu_to_le16(SAP_MSG_NOTIF_HOST_LINK_UP),
                .hdr.len = cpu_to_le16(sizeof(msg) - sizeof(msg.hdr)),
                .conn_info = {
                        .ssid_len = cpu_to_le32(conn_info->ssid_len),
                        .channel = conn_info->channel,
                        .band = conn_info->band,
                        .pairwise_cipher = cpu_to_le32(conn_info->pairwise_cipher),
                        .auth_mode = cpu_to_le32(conn_info->auth_mode),
                },
        };
        struct iwl_mei *mei;

        if (conn_info->ssid_len > ARRAY_SIZE(msg.conn_info.ssid))
                return;

        memcpy(msg.conn_info.ssid, conn_info->ssid, conn_info->ssid_len);
        memcpy(msg.conn_info.bssid, conn_info->bssid, ETH_ALEN);

        if (colloc_info) {
                msg.colloc_channel = colloc_info->channel;
                msg.colloc_band = colloc_info->channel <= 14 ? 0 : 1;
                memcpy(msg.colloc_bssid, colloc_info->bssid, ETH_ALEN);
        }

        mutex_lock(&iwl_mei_mutex);

        if (!iwl_mei_is_connected())
                goto out;

        mei = mei_cldev_get_drvdata(iwl_mei_global_cldev);

        if (!mei || !mei->amt_enabled)
                goto out;

        iwl_mei_send_sap_msg_payload(mei->cldev, &msg.hdr);

out:
        kfree(iwl_mei_cache.conn_info);
        iwl_mei_cache.conn_info =
                kmemdup(&msg.conn_info, sizeof(msg.conn_info), GFP_KERNEL);
        mutex_unlock(&iwl_mei_mutex);
}
EXPORT_SYMBOL_GPL(iwl_mei_host_associated);

void iwl_mei_host_disassociated(void)
{
        struct iwl_mei *mei;
        struct iwl_sap_notif_host_link_down msg = {
                .hdr.type = cpu_to_le16(SAP_MSG_NOTIF_HOST_LINK_DOWN),
                .hdr.len = cpu_to_le16(sizeof(msg) - sizeof(msg.hdr)),
                .type = HOST_LINK_DOWN_TYPE_TEMPORARY,
        };

        mutex_lock(&iwl_mei_mutex);

        if (!iwl_mei_is_connected())
                goto out;

        mei = mei_cldev_get_drvdata(iwl_mei_global_cldev);

        if (!mei || !mei->amt_enabled)
                goto out;

        iwl_mei_send_sap_msg_payload(mei->cldev, &msg.hdr);

out:
        kfree(iwl_mei_cache.conn_info);
        iwl_mei_cache.conn_info = NULL;
        mutex_unlock(&iwl_mei_mutex);
}
EXPORT_SYMBOL_GPL(iwl_mei_host_disassociated);

void iwl_mei_set_rfkill_state(bool hw_rfkill, bool sw_rfkill)
{
        struct iwl_mei *mei;
        u32 rfkill_state = 0;
        struct iwl_sap_msg_dw msg = {
                .hdr.type = cpu_to_le16(SAP_MSG_NOTIF_RADIO_STATE),
                .hdr.len = cpu_to_le16(sizeof(msg) - sizeof(msg.hdr)),
        };

        if (!sw_rfkill)
                rfkill_state |= SAP_SW_RFKILL_DEASSERTED;

        if (!hw_rfkill)
                rfkill_state |= SAP_HW_RFKILL_DEASSERTED;

        mutex_lock(&iwl_mei_mutex);

        if (!iwl_mei_is_connected())
                goto out;

        msg.val = cpu_to_le32(rfkill_state);

        mei = mei_cldev_get_drvdata(iwl_mei_global_cldev);

        if (!mei || !mei->amt_enabled)
                goto out;

        iwl_mei_send_sap_msg_payload(mei->cldev, &msg.hdr);

out:
        iwl_mei_cache.rf_kill = rfkill_state;
        mutex_unlock(&iwl_mei_mutex);
}
EXPORT_SYMBOL_GPL(iwl_mei_set_rfkill_state);

void iwl_mei_set_nic_info(const u8 *mac_address, const u8 *nvm_address)
{
        struct iwl_mei *mei;
        struct iwl_sap_notif_host_nic_info msg = {
                .hdr.type = cpu_to_le16(SAP_MSG_NOTIF_NIC_INFO),
                .hdr.len = cpu_to_le16(sizeof(msg) - sizeof(msg.hdr)),
        };

        mutex_lock(&iwl_mei_mutex);

        if (!iwl_mei_is_connected())
                goto out;

        ether_addr_copy(msg.mac_address, mac_address);
        ether_addr_copy(msg.nvm_address, nvm_address);

        mei = mei_cldev_get_drvdata(iwl_mei_global_cldev);

        if (!mei || !mei->amt_enabled)
                goto out;

        iwl_mei_send_sap_msg_payload(mei->cldev, &msg.hdr);

out:
        ether_addr_copy(iwl_mei_cache.mac_address, mac_address);
        ether_addr_copy(iwl_mei_cache.nvm_address, nvm_address);
        mutex_unlock(&iwl_mei_mutex);
}
EXPORT_SYMBOL_GPL(iwl_mei_set_nic_info);

void iwl_mei_set_country_code(u16 mcc)
{
        struct iwl_mei *mei;
        struct iwl_sap_notif_country_code msg = {
                .hdr.type = cpu_to_le16(SAP_MSG_NOTIF_COUNTRY_CODE),
                .hdr.len = cpu_to_le16(sizeof(msg) - sizeof(msg.hdr)),
                .mcc = cpu_to_le16(mcc),
        };

        mutex_lock(&iwl_mei_mutex);

        if (!iwl_mei_is_connected())
                goto out;

        mei = mei_cldev_get_drvdata(iwl_mei_global_cldev);

        if (!mei || !mei->amt_enabled)
                goto out;

        iwl_mei_send_sap_msg_payload(mei->cldev, &msg.hdr);

out:
        iwl_mei_cache.mcc = mcc;
        mutex_unlock(&iwl_mei_mutex);
}
EXPORT_SYMBOL_GPL(iwl_mei_set_country_code);

void iwl_mei_set_power_limit(const __le16 *power_limit)
{
        struct iwl_mei *mei;
        struct iwl_sap_notif_sar_limits msg = {
                .hdr.type = cpu_to_le16(SAP_MSG_NOTIF_SAR_LIMITS),
                .hdr.len = cpu_to_le16(sizeof(msg) - sizeof(msg.hdr)),
        };

        mutex_lock(&iwl_mei_mutex);

        if (!iwl_mei_is_connected())
                goto out;

        mei = mei_cldev_get_drvdata(iwl_mei_global_cldev);

        if (!mei || !mei->amt_enabled)
                goto out;

        memcpy(msg.sar_chain_info_table, power_limit, sizeof(msg.sar_chain_info_table));

        iwl_mei_send_sap_msg_payload(mei->cldev, &msg.hdr);

out:
        kfree(iwl_mei_cache.power_limit);
        iwl_mei_cache.power_limit = kmemdup(power_limit,
                                            sizeof(msg.sar_chain_info_table), GFP_KERNEL);
        mutex_unlock(&iwl_mei_mutex);
}
EXPORT_SYMBOL_GPL(iwl_mei_set_power_limit);

void iwl_mei_set_netdev(struct net_device *netdev)
{
        struct iwl_mei *mei;

        mutex_lock(&iwl_mei_mutex);

        if (!iwl_mei_is_connected()) {
                rcu_assign_pointer(iwl_mei_cache.netdev, netdev);
                goto out;
        }

        mei = mei_cldev_get_drvdata(iwl_mei_global_cldev);

        if (!mei)
                goto out;

        if (!netdev) {
                struct net_device *dev =
                        rcu_dereference_protected(iwl_mei_cache.netdev,
                                                  lockdep_is_held(&iwl_mei_mutex));

                if (!dev)
                        goto out;

                netdev_rx_handler_unregister(dev);
        }

        rcu_assign_pointer(iwl_mei_cache.netdev, netdev);

        if (netdev && mei->amt_enabled)
                netdev_rx_handler_register(netdev, iwl_mei_rx_handler, mei);

out:
        mutex_unlock(&iwl_mei_mutex);
}
EXPORT_SYMBOL_GPL(iwl_mei_set_netdev);

void iwl_mei_device_state(bool up)
{
        struct iwl_mei *mei;

        mutex_lock(&iwl_mei_mutex);

        if (!iwl_mei_is_connected())
                goto out;

        mei = mei_cldev_get_drvdata(iwl_mei_global_cldev);

        if (!mei)
                goto out;

        mei->device_down = !up;

        if (up || !mei->csme_taking_ownership)
                goto out;

        iwl_mei_send_sap_msg(mei->cldev,
                             SAP_MSG_NOTIF_CSME_OWNERSHIP_CONFIRMED);
        mei->csme_taking_ownership = false;
        schedule_delayed_work(&mei->ownership_dwork,
                              MEI_OWNERSHIP_RETAKE_TIMEOUT_MS);
out:
        mutex_unlock(&iwl_mei_mutex);
}
EXPORT_SYMBOL_GPL(iwl_mei_device_state);

int iwl_mei_register(void *priv, const struct iwl_mei_ops *ops)
{
        int ret;

        /*
         * We must have a non-NULL priv pointer to not crash when there are
         * multiple WiFi devices.
         */
        if (!priv)
                return -EINVAL;

        mutex_lock(&iwl_mei_mutex);

        /* do not allow registration if someone else already registered */
        if (iwl_mei_cache.priv || iwl_mei_cache.ops) {
                ret = -EBUSY;
                goto out;
        }

        iwl_mei_cache.priv = priv;
        iwl_mei_cache.ops = ops;

        if (iwl_mei_global_cldev) {
                struct iwl_mei *mei =
                        mei_cldev_get_drvdata(iwl_mei_global_cldev);

                /* we have already a SAP connection */
                if (iwl_mei_is_connected()) {
                        if (mei->amt_enabled)
                                iwl_mei_send_sap_msg(mei->cldev,
                                                     SAP_MSG_NOTIF_WIFIDR_UP);
                        ops->rfkill(priv, mei->link_prot_state, false);
                }
        }
        ret = 0;

out:
        mutex_unlock(&iwl_mei_mutex);
        return ret;
}
EXPORT_SYMBOL_GPL(iwl_mei_register);

void iwl_mei_start_unregister(void)
{
        mutex_lock(&iwl_mei_mutex);

        /* At this point, the wifi driver should have removed the netdev */
        if (rcu_access_pointer(iwl_mei_cache.netdev))
                pr_err("Still had a netdev pointer set upon unregister\n");

        kfree(iwl_mei_cache.conn_info);
        iwl_mei_cache.conn_info = NULL;
        kfree(iwl_mei_cache.power_limit);
        iwl_mei_cache.power_limit = NULL;
        iwl_mei_cache.ops = NULL;
        /* leave iwl_mei_cache.priv non-NULL to prevent any new registration */

        mutex_unlock(&iwl_mei_mutex);
}
EXPORT_SYMBOL_GPL(iwl_mei_start_unregister);

void iwl_mei_unregister_complete(void)
{
        mutex_lock(&iwl_mei_mutex);

        iwl_mei_cache.priv = NULL;

        if (iwl_mei_global_cldev) {
                struct iwl_mei *mei =
                        mei_cldev_get_drvdata(iwl_mei_global_cldev);

                if (mei->amt_enabled)
                        iwl_mei_send_sap_msg(mei->cldev,
                                             SAP_MSG_NOTIF_WIFIDR_DOWN);
                mei->got_ownership = false;
        }

        mutex_unlock(&iwl_mei_mutex);
}
EXPORT_SYMBOL_GPL(iwl_mei_unregister_complete);

#if IS_ENABLED(CONFIG_DEBUG_FS)

static ssize_t
iwl_mei_dbgfs_send_start_message_write(struct file *file,
                                       const char __user *user_buf,
                                       size_t count, loff_t *ppos)
{
        int ret;

        mutex_lock(&iwl_mei_mutex);

        if (!iwl_mei_global_cldev) {
                ret = -ENODEV;
                goto out;
        }

        ret = iwl_mei_send_start(iwl_mei_global_cldev);

out:
        mutex_unlock(&iwl_mei_mutex);
        return ret ?: count;
}

static const struct file_operations iwl_mei_dbgfs_send_start_message_ops = {
        .write = iwl_mei_dbgfs_send_start_message_write,
        .open = simple_open,
        .llseek = default_llseek,
};

static ssize_t iwl_mei_dbgfs_req_ownership_write(struct file *file,
                                                 const char __user *user_buf,
                                                 size_t count, loff_t *ppos)
{
        iwl_mei_get_ownership();

        return count;
}

static const struct file_operations iwl_mei_dbgfs_req_ownership_ops = {
        .write = iwl_mei_dbgfs_req_ownership_write,
        .open = simple_open,
        .llseek = default_llseek,
};

static void iwl_mei_dbgfs_register(struct iwl_mei *mei)
{
        mei->dbgfs_dir = debugfs_create_dir(KBUILD_MODNAME, NULL);

        if (!mei->dbgfs_dir)
                return;

        debugfs_create_ulong("status", S_IRUSR,
                             mei->dbgfs_dir, &iwl_mei_status);
        debugfs_create_file("send_start_message", S_IWUSR, mei->dbgfs_dir,
                            mei, &iwl_mei_dbgfs_send_start_message_ops);
        debugfs_create_file("req_ownership", S_IWUSR, mei->dbgfs_dir,
                            mei, &iwl_mei_dbgfs_req_ownership_ops);
}

static void iwl_mei_dbgfs_unregister(struct iwl_mei *mei)
{
        debugfs_remove_recursive(mei->dbgfs_dir);
        mei->dbgfs_dir = NULL;
}

#else

static void iwl_mei_dbgfs_register(struct iwl_mei *mei) {}
static void iwl_mei_dbgfs_unregister(struct iwl_mei *mei) {}

#endif /* CONFIG_DEBUG_FS */

static void iwl_mei_ownership_dwork(struct work_struct *wk)
{
        iwl_mei_get_ownership();
}

#define ALLOC_SHARED_MEM_RETRY_MAX_NUM  3

/*
 * iwl_mei_probe - the probe function called by the mei bus enumeration
 *
 * This allocates the data needed by iwlmei and sets a pointer to this data
 * into the mei_cl_device's drvdata.
 * It starts the SAP protocol by sending the SAP_ME_MSG_START without
 * waiting for the answer. The answer will be caught later by the Rx callback.
 */
static int iwl_mei_probe(struct mei_cl_device *cldev,
                         const struct mei_cl_device_id *id)
{
        int alloc_retry = ALLOC_SHARED_MEM_RETRY_MAX_NUM;
        struct iwl_mei *mei;
        int ret;

        mei = devm_kzalloc(&cldev->dev, sizeof(*mei), GFP_KERNEL);
        if (!mei)
                return -ENOMEM;

        init_waitqueue_head(&mei->get_nvm_wq);
        INIT_WORK(&mei->send_csa_msg_wk, iwl_mei_send_csa_msg_wk);
        INIT_DELAYED_WORK(&mei->csa_throttle_end_wk,
                          iwl_mei_csa_throttle_end_wk);
        init_waitqueue_head(&mei->get_ownership_wq);
        init_waitqueue_head(&mei->pldr_wq);
        spin_lock_init(&mei->data_q_lock);
        INIT_WORK(&mei->netdev_work, iwl_mei_netdev_work);
        INIT_DELAYED_WORK(&mei->ownership_dwork, iwl_mei_ownership_dwork);

        mei_cldev_set_drvdata(cldev, mei);
        mei->cldev = cldev;
        mei->device_down = true;

        do {
                ret = iwl_mei_alloc_shared_mem(cldev);
                if (!ret)
                        break;
                /*
                 * The CSME firmware needs to boot the internal WLAN client.
                 * This can take time in certain configurations (usually
                 * upon resume and when the whole CSME firmware is shut down
                 * during suspend).
                 *
                 * Wait a bit before retrying and hope we'll succeed next time.
                 */

                dev_dbg(&cldev->dev,
                        "Couldn't allocate the shared memory: %d, attempt %d / %d\n",
                        ret, alloc_retry, ALLOC_SHARED_MEM_RETRY_MAX_NUM);
                msleep(100);
                alloc_retry--;
        } while (alloc_retry);

        if (ret) {
                dev_err(&cldev->dev, "Couldn't allocate the shared memory: %d\n",
                        ret);
                goto free;
        }

        iwl_mei_init_shared_mem(mei);

        ret = iwl_mei_enable(cldev);
        if (ret)
                goto free_shared_mem;

        iwl_mei_dbgfs_register(mei);

        /*
         * We now have a Rx function in place, start the SAP protocol
         * we expect to get the SAP_ME_MSG_START_OK response later on.
         */
        mutex_lock(&iwl_mei_mutex);
        ret = iwl_mei_send_start(cldev);
        mutex_unlock(&iwl_mei_mutex);
        if (ret)
                goto debugfs_unregister;

        /* must be last */
        iwl_mei_global_cldev = cldev;

        return 0;

debugfs_unregister:
        iwl_mei_dbgfs_unregister(mei);
        mei_cldev_disable(cldev);
free_shared_mem:
        iwl_mei_free_shared_mem(cldev);
free:
        mei_cldev_set_drvdata(cldev, NULL);
        devm_kfree(&cldev->dev, mei);

        return ret;
}

#define SEND_SAP_MAX_WAIT_ITERATION 10
#define IWLMEI_DEVICE_DOWN_WAIT_ITERATION 50

static void iwl_mei_remove(struct mei_cl_device *cldev)
{
        struct iwl_mei *mei = mei_cldev_get_drvdata(cldev);
        int i;

        /*
         * We are being removed while the bus is active, it means we are
         * going to suspend/ shutdown, so the NIC will disappear.
         */
        if (mei_cldev_enabled(cldev) && iwl_mei_cache.ops) {
                unsigned int iter = IWLMEI_DEVICE_DOWN_WAIT_ITERATION;
                bool down = false;

                /*
                 * In case of suspend, wait for the mac to stop and don't remove
                 * the interface. This will allow the interface to come back
                 * on resume.
                 */
                while (!down && iter--) {
                        mdelay(1);

                        mutex_lock(&iwl_mei_mutex);
                        down = mei->device_down;
                        mutex_unlock(&iwl_mei_mutex);
                }

                if (!down)
                        iwl_mei_cache.ops->nic_stolen(iwl_mei_cache.priv);
        }

        if (rcu_access_pointer(iwl_mei_cache.netdev)) {
                struct net_device *dev;

                /*
                 * First take rtnl and only then the mutex to avoid an ABBA
                 * with iwl_mei_set_netdev()
                 */
                rtnl_lock();
                mutex_lock(&iwl_mei_mutex);

                /*
                 * If we are suspending and the wifi driver hasn't removed it's netdev
                 * yet, do it now. In any case, don't change the cache.netdev pointer.
                 */
                dev = rcu_dereference_protected(iwl_mei_cache.netdev,
                                                lockdep_is_held(&iwl_mei_mutex));

                netdev_rx_handler_unregister(dev);
                mutex_unlock(&iwl_mei_mutex);
                rtnl_unlock();
        }

        mutex_lock(&iwl_mei_mutex);

        /* Tell CSME that we are going down so that it won't access the
         * memory anymore, make sure this message goes through immediately.
         */
        mei->csa_throttled = false;
        iwl_mei_send_sap_msg(mei->cldev,
                             SAP_MSG_NOTIF_HOST_GOES_DOWN);

        for (i = 0; i < SEND_SAP_MAX_WAIT_ITERATION; i++) {
                if (!iwl_mei_host_to_me_data_pending(mei))
                        break;

                msleep(20);
        }

        /* If we couldn't make sure that CSME saw the HOST_GOES_DOWN
         * message, it means that it will probably keep reading memory
         * that we are going to unmap and free, expect IOMMU error
         * messages.
         */
        if (i == SEND_SAP_MAX_WAIT_ITERATION)
                dev_err(&mei->cldev->dev,
                        "Couldn't get ACK from CSME on HOST_GOES_DOWN message\n");

        mutex_unlock(&iwl_mei_mutex);

        /*
         * This looks strange, but this lock is taken here to make sure that
         * iwl_mei_add_data_to_ring called from the Tx path sees that we
         * clear the IWL_MEI_STATUS_SAP_CONNECTED bit.
         * Rx isn't a problem because the rx_handler can't be called after
         * having been unregistered.
         */
        spin_lock_bh(&mei->data_q_lock);
        clear_bit(IWL_MEI_STATUS_SAP_CONNECTED, &iwl_mei_status);
        spin_unlock_bh(&mei->data_q_lock);

        if (iwl_mei_cache.ops)
                iwl_mei_cache.ops->rfkill(iwl_mei_cache.priv, false, false);

        /*
         * mei_cldev_disable will return only after all the MEI Rx is done.
         * It must be called when iwl_mei_mutex is *not* held, since it waits
         * for our Rx handler to complete.
         * After it returns, no new Rx will start.
         */
        mei_cldev_disable(cldev);

        /*
         * Since the netdev was already removed and the netdev's removal
         * includes a call to synchronize_net() so that we know there won't be
         * any new Rx that will trigger the following workers.
         */
        cancel_work_sync(&mei->send_csa_msg_wk);
        cancel_delayed_work_sync(&mei->csa_throttle_end_wk);
        cancel_work_sync(&mei->netdev_work);
        cancel_delayed_work_sync(&mei->ownership_dwork);

        /*
         * If someone waits for the ownership, let him know that we are going
         * down and that we are not connected anymore. He'll be able to take
         * the device.
         */
        wake_up_all(&mei->get_ownership_wq);
        wake_up_all(&mei->pldr_wq);

        mutex_lock(&iwl_mei_mutex);

        iwl_mei_global_cldev = NULL;

        wake_up_all(&mei->get_nvm_wq);

        iwl_mei_free_shared_mem(cldev);

        iwl_mei_dbgfs_unregister(mei);

        mei_cldev_set_drvdata(cldev, NULL);

        kfree(mei->nvm);

        kfree(rcu_access_pointer(mei->filters));

        devm_kfree(&cldev->dev, mei);

        mutex_unlock(&iwl_mei_mutex);
}

static const struct mei_cl_device_id iwl_mei_tbl[] = {
        {
                .name = KBUILD_MODNAME,
                .uuid = MEI_WLAN_UUID,
                .version = MEI_CL_VERSION_ANY,
        },

        /* required last entry */
        { }
};

/*
 * Do not export the device table because this module is loaded by
 * iwlwifi's dependency.
 */

static struct mei_cl_driver iwl_mei_cl_driver = {
        .id_table = iwl_mei_tbl,
        .name = KBUILD_MODNAME,
        .probe = iwl_mei_probe,
        .remove = iwl_mei_remove,
};

module_mei_cl_driver(iwl_mei_cl_driver);