1 | // SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) |
2 | /* Copyright (C) 2016-2019 Netronome Systems, Inc. */ |
3 | |
4 | #include <linux/bitops.h> |
5 | |
6 | #include "ccm.h" |
7 | #include "nfp_app.h" |
8 | #include "nfp_net.h" |
9 | |
10 | #define ccm_warn(app, msg...) nn_dp_warn(&(app)->ctrl->dp, msg) |
11 | |
12 | #define NFP_CCM_TAG_ALLOC_SPAN (U16_MAX / 4) |
13 | |
14 | static bool nfp_ccm_all_tags_busy(struct nfp_ccm *ccm) |
15 | { |
16 | u16 used_tags; |
17 | |
18 | used_tags = ccm->tag_alloc_next - ccm->tag_alloc_last; |
19 | |
20 | return used_tags > NFP_CCM_TAG_ALLOC_SPAN; |
21 | } |
22 | |
23 | static int nfp_ccm_alloc_tag(struct nfp_ccm *ccm) |
24 | { |
25 | /* CCM is for FW communication which is request-reply. To make sure |
26 | * we don't reuse the message ID too early after timeout - limit the |
27 | * number of requests in flight. |
28 | */ |
29 | if (unlikely(nfp_ccm_all_tags_busy(ccm))) { |
30 | ccm_warn(ccm->app, "all FW request contexts busy!\n" ); |
31 | return -EAGAIN; |
32 | } |
33 | |
34 | WARN_ON(__test_and_set_bit(ccm->tag_alloc_next, ccm->tag_allocator)); |
35 | return ccm->tag_alloc_next++; |
36 | } |
37 | |
38 | static void nfp_ccm_free_tag(struct nfp_ccm *ccm, u16 tag) |
39 | { |
40 | WARN_ON(!__test_and_clear_bit(tag, ccm->tag_allocator)); |
41 | |
42 | while (!test_bit(ccm->tag_alloc_last, ccm->tag_allocator) && |
43 | ccm->tag_alloc_last != ccm->tag_alloc_next) |
44 | ccm->tag_alloc_last++; |
45 | } |
46 | |
47 | static struct sk_buff *__nfp_ccm_reply(struct nfp_ccm *ccm, u16 tag) |
48 | { |
49 | unsigned int msg_tag; |
50 | struct sk_buff *skb; |
51 | |
52 | skb_queue_walk(&ccm->replies, skb) { |
53 | msg_tag = nfp_ccm_get_tag(skb); |
54 | if (msg_tag == tag) { |
55 | nfp_ccm_free_tag(ccm, tag); |
56 | __skb_unlink(skb, list: &ccm->replies); |
57 | return skb; |
58 | } |
59 | } |
60 | |
61 | return NULL; |
62 | } |
63 | |
64 | static struct sk_buff * |
65 | nfp_ccm_reply(struct nfp_ccm *ccm, struct nfp_app *app, u16 tag) |
66 | { |
67 | struct sk_buff *skb; |
68 | |
69 | nfp_ctrl_lock(nn: app->ctrl); |
70 | skb = __nfp_ccm_reply(ccm, tag); |
71 | nfp_ctrl_unlock(nn: app->ctrl); |
72 | |
73 | return skb; |
74 | } |
75 | |
76 | static struct sk_buff * |
77 | nfp_ccm_reply_drop_tag(struct nfp_ccm *ccm, struct nfp_app *app, u16 tag) |
78 | { |
79 | struct sk_buff *skb; |
80 | |
81 | nfp_ctrl_lock(nn: app->ctrl); |
82 | skb = __nfp_ccm_reply(ccm, tag); |
83 | if (!skb) |
84 | nfp_ccm_free_tag(ccm, tag); |
85 | nfp_ctrl_unlock(nn: app->ctrl); |
86 | |
87 | return skb; |
88 | } |
89 | |
90 | static struct sk_buff * |
91 | nfp_ccm_wait_reply(struct nfp_ccm *ccm, struct nfp_app *app, |
92 | enum nfp_ccm_type type, int tag) |
93 | { |
94 | struct sk_buff *skb; |
95 | int i, err; |
96 | |
97 | for (i = 0; i < 50; i++) { |
98 | udelay(4); |
99 | skb = nfp_ccm_reply(ccm, app, tag); |
100 | if (skb) |
101 | return skb; |
102 | } |
103 | |
104 | err = wait_event_interruptible_timeout(ccm->wq, |
105 | skb = nfp_ccm_reply(ccm, app, |
106 | tag), |
107 | msecs_to_jiffies(5000)); |
108 | /* We didn't get a response - try last time and atomically drop |
109 | * the tag even if no response is matched. |
110 | */ |
111 | if (!skb) |
112 | skb = nfp_ccm_reply_drop_tag(ccm, app, tag); |
113 | if (err < 0) { |
114 | ccm_warn(app, "%s waiting for response to 0x%02x: %d\n" , |
115 | err == ERESTARTSYS ? "interrupted" : "error" , |
116 | type, err); |
117 | return ERR_PTR(error: err); |
118 | } |
119 | if (!skb) { |
120 | ccm_warn(app, "timeout waiting for response to 0x%02x\n" , type); |
121 | return ERR_PTR(error: -ETIMEDOUT); |
122 | } |
123 | |
124 | return skb; |
125 | } |
126 | |
127 | struct sk_buff * |
128 | nfp_ccm_communicate(struct nfp_ccm *ccm, struct sk_buff *skb, |
129 | enum nfp_ccm_type type, unsigned int reply_size) |
130 | { |
131 | struct nfp_app *app = ccm->app; |
132 | struct nfp_ccm_hdr *hdr; |
133 | int reply_type, tag; |
134 | |
135 | nfp_ctrl_lock(nn: app->ctrl); |
136 | tag = nfp_ccm_alloc_tag(ccm); |
137 | if (tag < 0) { |
138 | nfp_ctrl_unlock(nn: app->ctrl); |
139 | dev_kfree_skb_any(skb); |
140 | return ERR_PTR(error: tag); |
141 | } |
142 | |
143 | hdr = (void *)skb->data; |
144 | hdr->ver = NFP_CCM_ABI_VERSION; |
145 | hdr->type = type; |
146 | hdr->tag = cpu_to_be16(tag); |
147 | |
148 | __nfp_app_ctrl_tx(app, skb); |
149 | |
150 | nfp_ctrl_unlock(nn: app->ctrl); |
151 | |
152 | skb = nfp_ccm_wait_reply(ccm, app, type, tag); |
153 | if (IS_ERR(ptr: skb)) |
154 | return skb; |
155 | |
156 | reply_type = nfp_ccm_get_type(skb); |
157 | if (reply_type != __NFP_CCM_REPLY(type)) { |
158 | ccm_warn(app, "cmsg drop - wrong type 0x%02x != 0x%02lx!\n" , |
159 | reply_type, __NFP_CCM_REPLY(type)); |
160 | goto err_free; |
161 | } |
162 | /* 0 reply_size means caller will do the validation */ |
163 | if (reply_size && skb->len != reply_size) { |
164 | ccm_warn(app, "cmsg drop - type 0x%02x wrong size %d != %d!\n" , |
165 | type, skb->len, reply_size); |
166 | goto err_free; |
167 | } |
168 | |
169 | return skb; |
170 | err_free: |
171 | dev_kfree_skb_any(skb); |
172 | return ERR_PTR(error: -EIO); |
173 | } |
174 | |
175 | void nfp_ccm_rx(struct nfp_ccm *ccm, struct sk_buff *skb) |
176 | { |
177 | struct nfp_app *app = ccm->app; |
178 | unsigned int tag; |
179 | |
180 | if (unlikely(skb->len < sizeof(struct nfp_ccm_hdr))) { |
181 | ccm_warn(app, "cmsg drop - too short %d!\n" , skb->len); |
182 | goto err_free; |
183 | } |
184 | |
185 | nfp_ctrl_lock(nn: app->ctrl); |
186 | |
187 | tag = nfp_ccm_get_tag(skb); |
188 | if (unlikely(!test_bit(tag, ccm->tag_allocator))) { |
189 | ccm_warn(app, "cmsg drop - no one is waiting for tag %u!\n" , |
190 | tag); |
191 | goto err_unlock; |
192 | } |
193 | |
194 | __skb_queue_tail(list: &ccm->replies, newsk: skb); |
195 | wake_up_interruptible_all(&ccm->wq); |
196 | |
197 | nfp_ctrl_unlock(nn: app->ctrl); |
198 | return; |
199 | |
200 | err_unlock: |
201 | nfp_ctrl_unlock(nn: app->ctrl); |
202 | err_free: |
203 | dev_kfree_skb_any(skb); |
204 | } |
205 | |
206 | int nfp_ccm_init(struct nfp_ccm *ccm, struct nfp_app *app) |
207 | { |
208 | ccm->app = app; |
209 | skb_queue_head_init(list: &ccm->replies); |
210 | init_waitqueue_head(&ccm->wq); |
211 | return 0; |
212 | } |
213 | |
214 | void nfp_ccm_clean(struct nfp_ccm *ccm) |
215 | { |
216 | WARN_ON(!skb_queue_empty(&ccm->replies)); |
217 | } |
218 | |