]>
Commit | Line | Data |
---|---|---|
aa5a7aca IPG |
1 | /* |
2 | * Intel Wireless WiMAX Connection 2400m | |
3 | * Handle incoming traffic and deliver it to the control or data planes | |
4 | * | |
5 | * | |
6 | * Copyright (C) 2007-2008 Intel Corporation. All rights reserved. | |
7 | * | |
8 | * Redistribution and use in source and binary forms, with or without | |
9 | * modification, are permitted provided that the following conditions | |
10 | * are met: | |
11 | * | |
12 | * * Redistributions of source code must retain the above copyright | |
13 | * notice, this list of conditions and the following disclaimer. | |
14 | * * Redistributions in binary form must reproduce the above copyright | |
15 | * notice, this list of conditions and the following disclaimer in | |
16 | * the documentation and/or other materials provided with the | |
17 | * distribution. | |
18 | * * Neither the name of Intel Corporation nor the names of its | |
19 | * contributors may be used to endorse or promote products derived | |
20 | * from this software without specific prior written permission. | |
21 | * | |
22 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
23 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
24 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
25 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
26 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
27 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
28 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
29 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
30 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
31 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
32 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
33 | * | |
34 | * | |
35 | * Intel Corporation <linux-wimax@intel.com> | |
36 | * Yanir Lubetkin <yanirx.lubetkin@intel.com> | |
37 | * - Initial implementation | |
38 | * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> | |
39 | * - Use skb_clone(), break up processing in chunks | |
40 | * - Split transport/device specific | |
41 | * - Make buffer size dynamic to exert less memory pressure | |
42 | * | |
43 | * | |
44 | * This handles the RX path. | |
45 | * | |
46 | * We receive an RX message from the bus-specific driver, which | |
47 | * contains one or more payloads that have potentially different | |
48 | * destinataries (data or control paths). | |
49 | * | |
50 | * So we just take that payload from the transport specific code in | |
51 | * the form of an skb, break it up in chunks (a cloned skb each in the | |
52 | * case of network packets) and pass it to netdev or to the | |
53 | * command/ack handler (and from there to the WiMAX stack). | |
54 | * | |
55 | * PROTOCOL FORMAT | |
56 | * | |
57 | * The format of the buffer is: | |
58 | * | |
59 | * HEADER (struct i2400m_msg_hdr) | |
60 | * PAYLOAD DESCRIPTOR 0 (struct i2400m_pld) | |
61 | * PAYLOAD DESCRIPTOR 1 | |
62 | * ... | |
63 | * PAYLOAD DESCRIPTOR N | |
64 | * PAYLOAD 0 (raw bytes) | |
65 | * PAYLOAD 1 | |
66 | * ... | |
67 | * PAYLOAD N | |
68 | * | |
69 | * See tx.c for a deeper description on alignment requirements and | |
70 | * other fun facts of it. | |
71 | * | |
72 | * ROADMAP | |
73 | * | |
74 | * i2400m_rx | |
75 | * i2400m_rx_msg_hdr_check | |
76 | * i2400m_rx_pl_descr_check | |
77 | * i2400m_rx_payload | |
78 | * i2400m_net_rx | |
79 | * i2400m_rx_ctl | |
80 | * i2400m_msg_size_check | |
81 | * i2400m_report_hook_work [in a workqueue] | |
82 | * i2400m_report_hook | |
83 | * wimax_msg_to_user | |
84 | * i2400m_rx_ctl_ack | |
85 | * wimax_msg_to_user_alloc | |
86 | * i2400m_rx_trace | |
87 | * i2400m_msg_size_check | |
88 | * wimax_msg | |
89 | */ | |
90 | #include <linux/kernel.h> | |
91 | #include <linux/if_arp.h> | |
92 | #include <linux/netdevice.h> | |
93 | #include <linux/workqueue.h> | |
94 | #include "i2400m.h" | |
95 | ||
96 | ||
97 | #define D_SUBMODULE rx | |
98 | #include "debug-levels.h" | |
99 | ||
100 | struct i2400m_report_hook_args { | |
101 | struct sk_buff *skb_rx; | |
102 | const struct i2400m_l3l4_hdr *l3l4_hdr; | |
103 | size_t size; | |
104 | }; | |
105 | ||
106 | ||
107 | /* | |
108 | * Execute i2400m_report_hook in a workqueue | |
109 | * | |
110 | * Unpacks arguments from the deferred call, executes it and then | |
111 | * drops the references. | |
112 | * | |
113 | * Obvious NOTE: References are needed because we are a separate | |
114 | * thread; otherwise the buffer changes under us because it is | |
115 | * released by the original caller. | |
116 | */ | |
117 | static | |
118 | void i2400m_report_hook_work(struct work_struct *ws) | |
119 | { | |
120 | struct i2400m_work *iw = | |
121 | container_of(ws, struct i2400m_work, ws); | |
122 | struct i2400m_report_hook_args *args = (void *) iw->pl; | |
123 | i2400m_report_hook(iw->i2400m, args->l3l4_hdr, args->size); | |
124 | kfree_skb(args->skb_rx); | |
125 | i2400m_put(iw->i2400m); | |
126 | kfree(iw); | |
127 | } | |
128 | ||
129 | ||
130 | /* | |
131 | * Process an ack to a command | |
132 | * | |
133 | * @i2400m: device descriptor | |
134 | * @payload: pointer to message | |
135 | * @size: size of the message | |
136 | * | |
137 | * Pass the acknodledgment (in an skb) to the thread that is waiting | |
138 | * for it in i2400m->msg_completion. | |
139 | * | |
140 | * We need to coordinate properly with the thread waiting for the | |
141 | * ack. Check if it is waiting or if it is gone. We loose the spinlock | |
142 | * to avoid allocating on atomic contexts (yeah, could use GFP_ATOMIC, | |
143 | * but this is not so speed critical). | |
144 | */ | |
145 | static | |
146 | void i2400m_rx_ctl_ack(struct i2400m *i2400m, | |
147 | const void *payload, size_t size) | |
148 | { | |
149 | struct device *dev = i2400m_dev(i2400m); | |
150 | struct wimax_dev *wimax_dev = &i2400m->wimax_dev; | |
151 | unsigned long flags; | |
152 | struct sk_buff *ack_skb; | |
153 | ||
154 | /* Anyone waiting for an answer? */ | |
155 | spin_lock_irqsave(&i2400m->rx_lock, flags); | |
156 | if (i2400m->ack_skb != ERR_PTR(-EINPROGRESS)) { | |
157 | dev_err(dev, "Huh? reply to command with no waiters\n"); | |
158 | goto error_no_waiter; | |
159 | } | |
160 | spin_unlock_irqrestore(&i2400m->rx_lock, flags); | |
161 | ||
162 | ack_skb = wimax_msg_alloc(wimax_dev, NULL, payload, size, GFP_KERNEL); | |
163 | ||
164 | /* Check waiter didn't time out waiting for the answer... */ | |
165 | spin_lock_irqsave(&i2400m->rx_lock, flags); | |
166 | if (i2400m->ack_skb != ERR_PTR(-EINPROGRESS)) { | |
167 | d_printf(1, dev, "Huh? waiter for command reply cancelled\n"); | |
168 | goto error_waiter_cancelled; | |
169 | } | |
170 | if (ack_skb == NULL) { | |
171 | dev_err(dev, "CMD/GET/SET ack: cannot allocate SKB\n"); | |
172 | i2400m->ack_skb = ERR_PTR(-ENOMEM); | |
173 | } else | |
174 | i2400m->ack_skb = ack_skb; | |
175 | spin_unlock_irqrestore(&i2400m->rx_lock, flags); | |
176 | complete(&i2400m->msg_completion); | |
177 | return; | |
178 | ||
179 | error_waiter_cancelled: | |
180 | if (ack_skb) | |
181 | kfree_skb(ack_skb); | |
182 | error_no_waiter: | |
183 | spin_unlock_irqrestore(&i2400m->rx_lock, flags); | |
184 | return; | |
185 | } | |
186 | ||
187 | ||
188 | /* | |
189 | * Receive and process a control payload | |
190 | * | |
191 | * @i2400m: device descriptor | |
192 | * @skb_rx: skb that contains the payload (for reference counting) | |
193 | * @payload: pointer to message | |
194 | * @size: size of the message | |
195 | * | |
196 | * There are two types of control RX messages: reports (asynchronous, | |
197 | * like your every day interrupts) and 'acks' (reponses to a command, | |
198 | * get or set request). | |
199 | * | |
200 | * If it is a report, we run hooks on it (to extract information for | |
201 | * things we need to do in the driver) and then pass it over to the | |
202 | * WiMAX stack to send it to user space. | |
203 | * | |
204 | * NOTE: report processing is done in a workqueue specific to the | |
205 | * generic driver, to avoid deadlocks in the system. | |
206 | * | |
207 | * If it is not a report, it is an ack to a previously executed | |
208 | * command, set or get, so wake up whoever is waiting for it from | |
209 | * i2400m_msg_to_dev(). i2400m_rx_ctl_ack() takes care of that. | |
210 | * | |
211 | * Note that the sizes we pass to other functions from here are the | |
212 | * sizes of the _l3l4_hdr + payload, not full buffer sizes, as we have | |
213 | * verified in _msg_size_check() that they are congruent. | |
214 | * | |
215 | * For reports: We can't clone the original skb where the data is | |
216 | * because we need to send this up via netlink; netlink has to add | |
217 | * headers and we can't overwrite what's preceeding the payload...as | |
218 | * it is another message. So we just dup them. | |
219 | */ | |
220 | static | |
221 | void i2400m_rx_ctl(struct i2400m *i2400m, struct sk_buff *skb_rx, | |
222 | const void *payload, size_t size) | |
223 | { | |
224 | int result; | |
225 | struct device *dev = i2400m_dev(i2400m); | |
226 | const struct i2400m_l3l4_hdr *l3l4_hdr = payload; | |
227 | unsigned msg_type; | |
228 | ||
229 | result = i2400m_msg_size_check(i2400m, l3l4_hdr, size); | |
230 | if (result < 0) { | |
231 | dev_err(dev, "HW BUG? device sent a bad message: %d\n", | |
232 | result); | |
233 | goto error_check; | |
234 | } | |
235 | msg_type = le16_to_cpu(l3l4_hdr->type); | |
236 | d_printf(1, dev, "%s 0x%04x: %zu bytes\n", | |
237 | msg_type & I2400M_MT_REPORT_MASK ? "REPORT" : "CMD/SET/GET", | |
238 | msg_type, size); | |
239 | d_dump(2, dev, l3l4_hdr, size); | |
240 | if (msg_type & I2400M_MT_REPORT_MASK) { | |
241 | /* These hooks have to be ran serialized; as well, the | |
242 | * handling might force the execution of commands, and | |
243 | * that might cause reentrancy issues with | |
244 | * bus-specific subdrivers and workqueues. So we run | |
245 | * it in a separate workqueue. */ | |
246 | struct i2400m_report_hook_args args = { | |
247 | .skb_rx = skb_rx, | |
248 | .l3l4_hdr = l3l4_hdr, | |
249 | .size = size | |
250 | }; | |
251 | if (unlikely(i2400m->ready == 0)) /* only send if up */ | |
252 | return; | |
253 | skb_get(skb_rx); | |
254 | i2400m_queue_work(i2400m, i2400m_report_hook_work, | |
255 | GFP_KERNEL, &args, sizeof(args)); | |
256 | result = wimax_msg(&i2400m->wimax_dev, NULL, l3l4_hdr, size, | |
257 | GFP_KERNEL); | |
258 | if (result < 0) | |
259 | dev_err(dev, "error sending report to userspace: %d\n", | |
260 | result); | |
261 | } else /* an ack to a CMD, GET or SET */ | |
262 | i2400m_rx_ctl_ack(i2400m, payload, size); | |
263 | error_check: | |
264 | return; | |
265 | } | |
266 | ||
267 | ||
268 | ||
269 | ||
270 | /* | |
271 | * Receive and send up a trace | |
272 | * | |
273 | * @i2400m: device descriptor | |
274 | * @skb_rx: skb that contains the trace (for reference counting) | |
275 | * @payload: pointer to trace message inside the skb | |
276 | * @size: size of the message | |
277 | * | |
278 | * THe i2400m might produce trace information (diagnostics) and we | |
279 | * send them through a different kernel-to-user pipe (to avoid | |
280 | * clogging it). | |
281 | * | |
282 | * As in i2400m_rx_ctl(), we can't clone the original skb where the | |
283 | * data is because we need to send this up via netlink; netlink has to | |
284 | * add headers and we can't overwrite what's preceeding the | |
285 | * payload...as it is another message. So we just dup them. | |
286 | */ | |
287 | static | |
288 | void i2400m_rx_trace(struct i2400m *i2400m, | |
289 | const void *payload, size_t size) | |
290 | { | |
291 | int result; | |
292 | struct device *dev = i2400m_dev(i2400m); | |
293 | struct wimax_dev *wimax_dev = &i2400m->wimax_dev; | |
294 | const struct i2400m_l3l4_hdr *l3l4_hdr = payload; | |
295 | unsigned msg_type; | |
296 | ||
297 | result = i2400m_msg_size_check(i2400m, l3l4_hdr, size); | |
298 | if (result < 0) { | |
299 | dev_err(dev, "HW BUG? device sent a bad trace message: %d\n", | |
300 | result); | |
301 | goto error_check; | |
302 | } | |
303 | msg_type = le16_to_cpu(l3l4_hdr->type); | |
304 | d_printf(1, dev, "Trace %s 0x%04x: %zu bytes\n", | |
305 | msg_type & I2400M_MT_REPORT_MASK ? "REPORT" : "CMD/SET/GET", | |
306 | msg_type, size); | |
307 | d_dump(2, dev, l3l4_hdr, size); | |
308 | if (unlikely(i2400m->ready == 0)) /* only send if up */ | |
309 | return; | |
310 | result = wimax_msg(wimax_dev, "trace", l3l4_hdr, size, GFP_KERNEL); | |
311 | if (result < 0) | |
312 | dev_err(dev, "error sending trace to userspace: %d\n", | |
313 | result); | |
314 | error_check: | |
315 | return; | |
316 | } | |
317 | ||
318 | ||
319 | /* | |
320 | * Act on a received payload | |
321 | * | |
322 | * @i2400m: device instance | |
323 | * @skb_rx: skb where the transaction was received | |
324 | * @single: 1 if there is only one payload, 0 otherwise | |
325 | * @pld: payload descriptor | |
326 | * @payload: payload data | |
327 | * | |
328 | * Upon reception of a payload, look at its guts in the payload | |
329 | * descriptor and decide what to do with it. | |
330 | */ | |
331 | static | |
332 | void i2400m_rx_payload(struct i2400m *i2400m, struct sk_buff *skb_rx, | |
333 | unsigned single, const struct i2400m_pld *pld, | |
334 | const void *payload) | |
335 | { | |
336 | struct device *dev = i2400m_dev(i2400m); | |
337 | size_t pl_size = i2400m_pld_size(pld); | |
338 | enum i2400m_pt pl_type = i2400m_pld_type(pld); | |
339 | ||
340 | switch (pl_type) { | |
341 | case I2400M_PT_DATA: | |
342 | d_printf(3, dev, "RX: data payload %zu bytes\n", pl_size); | |
343 | i2400m_net_rx(i2400m, skb_rx, single, payload, pl_size); | |
344 | break; | |
345 | case I2400M_PT_CTRL: | |
346 | i2400m_rx_ctl(i2400m, skb_rx, payload, pl_size); | |
347 | break; | |
348 | case I2400M_PT_TRACE: | |
349 | i2400m_rx_trace(i2400m, payload, pl_size); | |
350 | break; | |
351 | default: /* Anything else shouldn't come to the host */ | |
352 | if (printk_ratelimit()) | |
353 | dev_err(dev, "RX: HW BUG? unexpected payload type %u\n", | |
354 | pl_type); | |
355 | } | |
356 | } | |
357 | ||
358 | ||
359 | /* | |
360 | * Check a received transaction's message header | |
361 | * | |
362 | * @i2400m: device descriptor | |
363 | * @msg_hdr: message header | |
364 | * @buf_size: size of the received buffer | |
365 | * | |
366 | * Check that the declarations done by a RX buffer message header are | |
367 | * sane and consistent with the amount of data that was received. | |
368 | */ | |
369 | static | |
370 | int i2400m_rx_msg_hdr_check(struct i2400m *i2400m, | |
371 | const struct i2400m_msg_hdr *msg_hdr, | |
372 | size_t buf_size) | |
373 | { | |
374 | int result = -EIO; | |
375 | struct device *dev = i2400m_dev(i2400m); | |
376 | if (buf_size < sizeof(*msg_hdr)) { | |
377 | dev_err(dev, "RX: HW BUG? message with short header (%zu " | |
378 | "vs %zu bytes expected)\n", buf_size, sizeof(*msg_hdr)); | |
379 | goto error; | |
380 | } | |
381 | if (msg_hdr->barker != cpu_to_le32(I2400M_D2H_MSG_BARKER)) { | |
382 | dev_err(dev, "RX: HW BUG? message received with unknown " | |
383 | "barker 0x%08x (buf_size %zu bytes)\n", | |
384 | le32_to_cpu(msg_hdr->barker), buf_size); | |
385 | goto error; | |
386 | } | |
387 | if (msg_hdr->num_pls == 0) { | |
388 | dev_err(dev, "RX: HW BUG? zero payload packets in message\n"); | |
389 | goto error; | |
390 | } | |
391 | if (le16_to_cpu(msg_hdr->num_pls) > I2400M_MAX_PLS_IN_MSG) { | |
392 | dev_err(dev, "RX: HW BUG? message contains more payload " | |
393 | "than maximum; ignoring.\n"); | |
394 | goto error; | |
395 | } | |
396 | result = 0; | |
397 | error: | |
398 | return result; | |
399 | } | |
400 | ||
401 | ||
402 | /* | |
403 | * Check a payload descriptor against the received data | |
404 | * | |
405 | * @i2400m: device descriptor | |
406 | * @pld: payload descriptor | |
407 | * @pl_itr: offset (in bytes) in the received buffer the payload is | |
408 | * located | |
409 | * @buf_size: size of the received buffer | |
410 | * | |
411 | * Given a payload descriptor (part of a RX buffer), check it is sane | |
412 | * and that the data it declares fits in the buffer. | |
413 | */ | |
414 | static | |
415 | int i2400m_rx_pl_descr_check(struct i2400m *i2400m, | |
416 | const struct i2400m_pld *pld, | |
417 | size_t pl_itr, size_t buf_size) | |
418 | { | |
419 | int result = -EIO; | |
420 | struct device *dev = i2400m_dev(i2400m); | |
421 | size_t pl_size = i2400m_pld_size(pld); | |
422 | enum i2400m_pt pl_type = i2400m_pld_type(pld); | |
423 | ||
424 | if (pl_size > i2400m->bus_pl_size_max) { | |
425 | dev_err(dev, "RX: HW BUG? payload @%zu: size %zu is " | |
426 | "bigger than maximum %zu; ignoring message\n", | |
427 | pl_itr, pl_size, i2400m->bus_pl_size_max); | |
428 | goto error; | |
429 | } | |
430 | if (pl_itr + pl_size > buf_size) { /* enough? */ | |
431 | dev_err(dev, "RX: HW BUG? payload @%zu: size %zu " | |
432 | "goes beyond the received buffer " | |
433 | "size (%zu bytes); ignoring message\n", | |
434 | pl_itr, pl_size, buf_size); | |
435 | goto error; | |
436 | } | |
437 | if (pl_type >= I2400M_PT_ILLEGAL) { | |
438 | dev_err(dev, "RX: HW BUG? illegal payload type %u; " | |
439 | "ignoring message\n", pl_type); | |
440 | goto error; | |
441 | } | |
442 | result = 0; | |
443 | error: | |
444 | return result; | |
445 | } | |
446 | ||
447 | ||
448 | /** | |
449 | * i2400m_rx - Receive a buffer of data from the device | |
450 | * | |
451 | * @i2400m: device descriptor | |
452 | * @skb: skbuff where the data has been received | |
453 | * | |
454 | * Parse in a buffer of data that contains an RX message sent from the | |
455 | * device. See the file header for the format. Run all checks on the | |
456 | * buffer header, then run over each payload's descriptors, verify | |
457 | * their consistency and act on each payload's contents. If | |
458 | * everything is succesful, update the device's statistics. | |
459 | * | |
460 | * Note: You need to set the skb to contain only the length of the | |
461 | * received buffer; for that, use skb_trim(skb, RECEIVED_SIZE). | |
462 | * | |
463 | * Returns: | |
464 | * | |
465 | * 0 if ok, < 0 errno on error | |
466 | * | |
467 | * If ok, this function owns now the skb and the caller DOESN'T have | |
468 | * to run kfree_skb() on it. However, on error, the caller still owns | |
469 | * the skb and it is responsible for releasing it. | |
470 | */ | |
471 | int i2400m_rx(struct i2400m *i2400m, struct sk_buff *skb) | |
472 | { | |
473 | int i, result; | |
474 | struct device *dev = i2400m_dev(i2400m); | |
475 | const struct i2400m_msg_hdr *msg_hdr; | |
476 | size_t pl_itr, pl_size, skb_len; | |
477 | unsigned long flags; | |
478 | unsigned num_pls; | |
479 | ||
480 | skb_len = skb->len; | |
481 | d_fnstart(4, dev, "(i2400m %p skb %p [size %zu])\n", | |
482 | i2400m, skb, skb_len); | |
483 | result = -EIO; | |
484 | msg_hdr = (void *) skb->data; | |
485 | result = i2400m_rx_msg_hdr_check(i2400m, msg_hdr, skb->len); | |
486 | if (result < 0) | |
487 | goto error_msg_hdr_check; | |
488 | result = -EIO; | |
489 | num_pls = le16_to_cpu(msg_hdr->num_pls); | |
490 | pl_itr = sizeof(*msg_hdr) + /* Check payload descriptor(s) */ | |
491 | num_pls * sizeof(msg_hdr->pld[0]); | |
492 | pl_itr = ALIGN(pl_itr, I2400M_PL_PAD); | |
493 | if (pl_itr > skb->len) { /* got all the payload descriptors? */ | |
494 | dev_err(dev, "RX: HW BUG? message too short (%u bytes) for " | |
495 | "%u payload descriptors (%zu each, total %zu)\n", | |
496 | skb->len, num_pls, sizeof(msg_hdr->pld[0]), pl_itr); | |
497 | goto error_pl_descr_short; | |
498 | } | |
499 | /* Walk each payload payload--check we really got it */ | |
500 | for (i = 0; i < num_pls; i++) { | |
501 | /* work around old gcc warnings */ | |
502 | pl_size = i2400m_pld_size(&msg_hdr->pld[i]); | |
503 | result = i2400m_rx_pl_descr_check(i2400m, &msg_hdr->pld[i], | |
504 | pl_itr, skb->len); | |
505 | if (result < 0) | |
506 | goto error_pl_descr_check; | |
507 | i2400m_rx_payload(i2400m, skb, num_pls == 1, &msg_hdr->pld[i], | |
508 | skb->data + pl_itr); | |
509 | pl_itr += ALIGN(pl_size, I2400M_PL_PAD); | |
510 | cond_resched(); /* Don't monopolize */ | |
511 | } | |
512 | kfree_skb(skb); | |
513 | /* Update device statistics */ | |
514 | spin_lock_irqsave(&i2400m->rx_lock, flags); | |
515 | i2400m->rx_pl_num += i; | |
516 | if (i > i2400m->rx_pl_max) | |
517 | i2400m->rx_pl_max = i; | |
518 | if (i < i2400m->rx_pl_min) | |
519 | i2400m->rx_pl_min = i; | |
520 | i2400m->rx_num++; | |
521 | i2400m->rx_size_acc += skb->len; | |
522 | if (skb->len < i2400m->rx_size_min) | |
523 | i2400m->rx_size_min = skb->len; | |
524 | if (skb->len > i2400m->rx_size_max) | |
525 | i2400m->rx_size_max = skb->len; | |
526 | spin_unlock_irqrestore(&i2400m->rx_lock, flags); | |
527 | error_pl_descr_check: | |
528 | error_pl_descr_short: | |
529 | error_msg_hdr_check: | |
530 | d_fnend(4, dev, "(i2400m %p skb %p [size %zu]) = %d\n", | |
531 | i2400m, skb, skb_len, result); | |
532 | return result; | |
533 | } | |
534 | EXPORT_SYMBOL_GPL(i2400m_rx); |