| // 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)); |
| } |