root/drivers/net/ethernet/netronome/nfp/ccm.c
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
/* Copyright (C) 2016-2019 Netronome Systems, Inc. */

#include <linux/bitops.h>

#include "ccm.h"
#include "nfp_app.h"
#include "nfp_net.h"

#define ccm_warn(app, msg...)   nn_dp_warn(&(app)->ctrl->dp, msg)

#define NFP_CCM_TAG_ALLOC_SPAN  (U16_MAX / 4)

static bool nfp_ccm_all_tags_busy(struct nfp_ccm *ccm)
{
        u16 used_tags;

        used_tags = ccm->tag_alloc_next - ccm->tag_alloc_last;

        return used_tags > NFP_CCM_TAG_ALLOC_SPAN;
}

static int nfp_ccm_alloc_tag(struct nfp_ccm *ccm)
{
        /* CCM is for FW communication which is request-reply.  To make sure
         * we don't reuse the message ID too early after timeout - limit the
         * number of requests in flight.
         */
        if (unlikely(nfp_ccm_all_tags_busy(ccm))) {
                ccm_warn(ccm->app, "all FW request contexts busy!\n");
                return -EAGAIN;
        }

        WARN_ON(__test_and_set_bit(ccm->tag_alloc_next, ccm->tag_allocator));
        return ccm->tag_alloc_next++;
}

static void nfp_ccm_free_tag(struct nfp_ccm *ccm, u16 tag)
{
        WARN_ON(!__test_and_clear_bit(tag, ccm->tag_allocator));

        while (!test_bit(ccm->tag_alloc_last, ccm->tag_allocator) &&
               ccm->tag_alloc_last != ccm->tag_alloc_next)
                ccm->tag_alloc_last++;
}

static struct sk_buff *__nfp_ccm_reply(struct nfp_ccm *ccm, u16 tag)
{
        unsigned int msg_tag;
        struct sk_buff *skb;

        skb_queue_walk(&ccm->replies, skb) {
                msg_tag = nfp_ccm_get_tag(skb);
                if (msg_tag == tag) {
                        nfp_ccm_free_tag(ccm, tag);
                        __skb_unlink(skb, &ccm->replies);
                        return skb;
                }
        }

        return NULL;
}

static struct sk_buff *
nfp_ccm_reply(struct nfp_ccm *ccm, struct nfp_app *app, u16 tag)
{
        struct sk_buff *skb;

        nfp_ctrl_lock(app->ctrl);
        skb = __nfp_ccm_reply(ccm, tag);
        nfp_ctrl_unlock(app->ctrl);

        return skb;
}

static struct sk_buff *
nfp_ccm_reply_drop_tag(struct nfp_ccm *ccm, struct nfp_app *app, u16 tag)
{
        struct sk_buff *skb;

        nfp_ctrl_lock(app->ctrl);
        skb = __nfp_ccm_reply(ccm, tag);
        if (!skb)
                nfp_ccm_free_tag(ccm, tag);
        nfp_ctrl_unlock(app->ctrl);

        return skb;
}

static struct sk_buff *
nfp_ccm_wait_reply(struct nfp_ccm *ccm, struct nfp_app *app,
                   enum nfp_ccm_type type, int tag)
{
        struct sk_buff *skb;
        int i, err;

        for (i = 0; i < 50; i++) {
                udelay(4);
                skb = nfp_ccm_reply(ccm, app, tag);
                if (skb)
                        return skb;
        }

        err = wait_event_interruptible_timeout(ccm->wq,
                                               skb = nfp_ccm_reply(ccm, app,
                                                                   tag),
                                               msecs_to_jiffies(5000));
        /* We didn't get a response - try last time and atomically drop
         * the tag even if no response is matched.
         */
        if (!skb)
                skb = nfp_ccm_reply_drop_tag(ccm, app, tag);
        if (err < 0) {
                ccm_warn(app, "%s waiting for response to 0x%02x: %d\n",
                         err == ERESTARTSYS ? "interrupted" : "error",
                         type, err);
                return ERR_PTR(err);
        }
        if (!skb) {
                ccm_warn(app, "timeout waiting for response to 0x%02x\n", type);
                return ERR_PTR(-ETIMEDOUT);
        }

        return skb;
}

struct sk_buff *
nfp_ccm_communicate(struct nfp_ccm *ccm, struct sk_buff *skb,
                    enum nfp_ccm_type type, unsigned int reply_size)
{
        struct nfp_app *app = ccm->app;
        struct nfp_ccm_hdr *hdr;
        int reply_type, tag;

        nfp_ctrl_lock(app->ctrl);
        tag = nfp_ccm_alloc_tag(ccm);
        if (tag < 0) {
                nfp_ctrl_unlock(app->ctrl);
                dev_kfree_skb_any(skb);
                return ERR_PTR(tag);
        }

        hdr = (void *)skb->data;
        hdr->ver = NFP_CCM_ABI_VERSION;
        hdr->type = type;
        hdr->tag = cpu_to_be16(tag);

        __nfp_app_ctrl_tx(app, skb);

        nfp_ctrl_unlock(app->ctrl);

        skb = nfp_ccm_wait_reply(ccm, app, type, tag);
        if (IS_ERR(skb))
                return skb;

        reply_type = nfp_ccm_get_type(skb);
        if (reply_type != __NFP_CCM_REPLY(type)) {
                ccm_warn(app, "cmsg drop - wrong type 0x%02x != 0x%02lx!\n",
                         reply_type, __NFP_CCM_REPLY(type));
                goto err_free;
        }
        /* 0 reply_size means caller will do the validation */
        if (reply_size && skb->len != reply_size) {
                ccm_warn(app, "cmsg drop - type 0x%02x wrong size %d != %d!\n",
                         type, skb->len, reply_size);
                goto err_free;
        }

        return skb;
err_free:
        dev_kfree_skb_any(skb);
        return ERR_PTR(-EIO);
}

void nfp_ccm_rx(struct nfp_ccm *ccm, struct sk_buff *skb)
{
        struct nfp_app *app = ccm->app;
        unsigned int tag;

        if (unlikely(skb->len < sizeof(struct nfp_ccm_hdr))) {
                ccm_warn(app, "cmsg drop - too short %d!\n", skb->len);
                goto err_free;
        }

        nfp_ctrl_lock(app->ctrl);

        tag = nfp_ccm_get_tag(skb);
        if (unlikely(!test_bit(tag, ccm->tag_allocator))) {
                ccm_warn(app, "cmsg drop - no one is waiting for tag %u!\n",
                         tag);
                goto err_unlock;
        }

        __skb_queue_tail(&ccm->replies, skb);
        wake_up_interruptible_all(&ccm->wq);

        nfp_ctrl_unlock(app->ctrl);
        return;

err_unlock:
        nfp_ctrl_unlock(app->ctrl);
err_free:
        dev_kfree_skb_any(skb);
}

int nfp_ccm_init(struct nfp_ccm *ccm, struct nfp_app *app)
{
        ccm->app = app;
        skb_queue_head_init(&ccm->replies);
        init_waitqueue_head(&ccm->wq);
        return 0;
}

void nfp_ccm_clean(struct nfp_ccm *ccm)
{
        WARN_ON(!skb_queue_empty(&ccm->replies));
}