// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) /* Copyright (C) 2016-2019 Netronome Systems, Inc. */ #include #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)); }