diff options
Diffstat (limited to 'drivers/net/wimax/i2400m/rx.c')
-rw-r--r-- | drivers/net/wimax/i2400m/rx.c | 117 |
1 files changed, 109 insertions, 8 deletions
diff --git a/drivers/net/wimax/i2400m/rx.c b/drivers/net/wimax/i2400m/rx.c index c62b8c564161..cd525066d4b7 100644 --- a/drivers/net/wimax/i2400m/rx.c +++ b/drivers/net/wimax/i2400m/rx.c | |||
@@ -69,6 +69,22 @@ | |||
69 | * See tx.c for a deeper description on alignment requirements and | 69 | * See tx.c for a deeper description on alignment requirements and |
70 | * other fun facts of it. | 70 | * other fun facts of it. |
71 | * | 71 | * |
72 | * DATA PACKETS | ||
73 | * | ||
74 | * In firmwares <= v1.3, data packets have no header for RX, but they | ||
75 | * do for TX (currently unused). | ||
76 | * | ||
77 | * In firmware >= 1.4, RX packets have an extended header (16 | ||
78 | * bytes). This header conveys information for management of host | ||
79 | * reordering of packets (the device offloads storage of the packets | ||
80 | * for reordering to the host). | ||
81 | * | ||
82 | * Currently this information is not used as the current code doesn't | ||
83 | * enable host reordering. | ||
84 | * | ||
85 | * The header is used as dummy space to emulate an ethernet header and | ||
86 | * thus be able to act as an ethernet device without having to reallocate. | ||
87 | * | ||
72 | * ROADMAP | 88 | * ROADMAP |
73 | * | 89 | * |
74 | * i2400m_rx | 90 | * i2400m_rx |
@@ -76,6 +92,8 @@ | |||
76 | * i2400m_rx_pl_descr_check | 92 | * i2400m_rx_pl_descr_check |
77 | * i2400m_rx_payload | 93 | * i2400m_rx_payload |
78 | * i2400m_net_rx | 94 | * i2400m_net_rx |
95 | * i2400m_rx_edata | ||
96 | * i2400m_net_erx | ||
79 | * i2400m_rx_ctl | 97 | * i2400m_rx_ctl |
80 | * i2400m_msg_size_check | 98 | * i2400m_msg_size_check |
81 | * i2400m_report_hook_work [in a workqueue] | 99 | * i2400m_report_hook_work [in a workqueue] |
@@ -264,8 +282,6 @@ error_check: | |||
264 | } | 282 | } |
265 | 283 | ||
266 | 284 | ||
267 | |||
268 | |||
269 | /* | 285 | /* |
270 | * Receive and send up a trace | 286 | * Receive and send up a trace |
271 | * | 287 | * |
@@ -314,32 +330,112 @@ error_check: | |||
314 | return; | 330 | return; |
315 | } | 331 | } |
316 | 332 | ||
333 | /* | ||
334 | * Receive and send up an extended data packet | ||
335 | * | ||
336 | * @i2400m: device descriptor | ||
337 | * @skb_rx: skb that contains the extended data packet | ||
338 | * @single_last: 1 if the payload is the only one or the last one of | ||
339 | * the skb. | ||
340 | * @payload: pointer to the packet's data inside the skb | ||
341 | * @size: size of the payload | ||
342 | * | ||
343 | * Starting in v1.4 of the i2400m's firmware, the device can send data | ||
344 | * packets to the host in an extended format that; this incudes a 16 | ||
345 | * byte header (struct i2400m_pl_edata_hdr). Using this header's space | ||
346 | * we can fake ethernet headers for ethernet device emulation without | ||
347 | * having to copy packets around. | ||
348 | * | ||
349 | * This function handles said path. | ||
350 | */ | ||
351 | static | ||
352 | void i2400m_rx_edata(struct i2400m *i2400m, struct sk_buff *skb_rx, | ||
353 | unsigned single_last, const void *payload, size_t size) | ||
354 | { | ||
355 | struct device *dev = i2400m_dev(i2400m); | ||
356 | const struct i2400m_pl_edata_hdr *hdr = payload; | ||
357 | struct net_device *net_dev = i2400m->wimax_dev.net_dev; | ||
358 | struct sk_buff *skb; | ||
359 | enum i2400m_cs cs; | ||
360 | unsigned reorder_needed; | ||
361 | |||
362 | d_fnstart(4, dev, "(i2400m %p skb_rx %p single %u payload %p " | ||
363 | "size %zu)\n", i2400m, skb_rx, single_last, payload, size); | ||
364 | if (size < sizeof(*hdr)) { | ||
365 | dev_err(dev, "ERX: HW BUG? message with short header (%zu " | ||
366 | "vs %zu bytes expected)\n", size, sizeof(*hdr)); | ||
367 | goto error; | ||
368 | } | ||
369 | reorder_needed = le32_to_cpu(hdr->reorder & I2400M_REORDER_NEEDED); | ||
370 | cs = hdr->cs; | ||
371 | if (reorder_needed) { | ||
372 | dev_err(dev, "ERX: HW BUG? reorder needed, it was disabled\n"); | ||
373 | goto error; | ||
374 | } | ||
375 | /* ok, so now decide if we want to clone or reuse the skb, | ||
376 | * pull and trim it so the beginning is the space for the eth | ||
377 | * header and pass it to i2400m_net_erx() for the stack */ | ||
378 | if (single_last) { | ||
379 | skb = skb_get(skb_rx); | ||
380 | d_printf(3, dev, "ERX: reusing single payload skb %p\n", skb); | ||
381 | } else { | ||
382 | skb = skb_clone(skb_rx, GFP_KERNEL); | ||
383 | d_printf(3, dev, "ERX: cloning %p\n", skb); | ||
384 | if (skb == NULL) { | ||
385 | dev_err(dev, "ERX: no memory to clone skb\n"); | ||
386 | net_dev->stats.rx_dropped++; | ||
387 | goto error_skb_clone; | ||
388 | } | ||
389 | } | ||
390 | /* now we have to pull and trim so that the skb points to the | ||
391 | * beginning of the IP packet; the netdev part will add the | ||
392 | * ethernet header as needed. */ | ||
393 | BUILD_BUG_ON(ETH_HLEN > sizeof(*hdr)); | ||
394 | skb_pull(skb, payload + sizeof(*hdr) - (void *) skb->data); | ||
395 | skb_trim(skb, (void *) skb_end_pointer(skb) - payload + sizeof(*hdr)); | ||
396 | i2400m_net_erx(i2400m, skb, cs); | ||
397 | error_skb_clone: | ||
398 | error: | ||
399 | d_fnend(4, dev, "(i2400m %p skb_rx %p single %u payload %p " | ||
400 | "size %zu) = void\n", i2400m, skb_rx, single_last, payload, size); | ||
401 | return; | ||
402 | } | ||
403 | |||
404 | |||
405 | |||
317 | 406 | ||
318 | /* | 407 | /* |
319 | * Act on a received payload | 408 | * Act on a received payload |
320 | * | 409 | * |
321 | * @i2400m: device instance | 410 | * @i2400m: device instance |
322 | * @skb_rx: skb where the transaction was received | 411 | * @skb_rx: skb where the transaction was received |
323 | * @single: 1 if there is only one payload, 0 otherwise | 412 | * @single_last: 1 this is the only payload or the last one (so the |
413 | * skb can be reused instead of cloned). | ||
324 | * @pld: payload descriptor | 414 | * @pld: payload descriptor |
325 | * @payload: payload data | 415 | * @payload: payload data |
326 | * | 416 | * |
327 | * Upon reception of a payload, look at its guts in the payload | 417 | * Upon reception of a payload, look at its guts in the payload |
328 | * descriptor and decide what to do with it. | 418 | * descriptor and decide what to do with it. If it is a single payload |
419 | * skb or if the last skb is a data packet, the skb will be referenced | ||
420 | * and modified (so it doesn't have to be cloned). | ||
329 | */ | 421 | */ |
330 | static | 422 | static |
331 | void i2400m_rx_payload(struct i2400m *i2400m, struct sk_buff *skb_rx, | 423 | void i2400m_rx_payload(struct i2400m *i2400m, struct sk_buff *skb_rx, |
332 | unsigned single, const struct i2400m_pld *pld, | 424 | unsigned single_last, const struct i2400m_pld *pld, |
333 | const void *payload) | 425 | const void *payload) |
334 | { | 426 | { |
335 | struct device *dev = i2400m_dev(i2400m); | 427 | struct device *dev = i2400m_dev(i2400m); |
336 | size_t pl_size = i2400m_pld_size(pld); | 428 | size_t pl_size = i2400m_pld_size(pld); |
337 | enum i2400m_pt pl_type = i2400m_pld_type(pld); | 429 | enum i2400m_pt pl_type = i2400m_pld_type(pld); |
338 | 430 | ||
431 | d_printf(7, dev, "RX: received payload type %u, %zu bytes\n", | ||
432 | pl_type, pl_size); | ||
433 | d_dump(8, dev, payload, pl_size); | ||
434 | |||
339 | switch (pl_type) { | 435 | switch (pl_type) { |
340 | case I2400M_PT_DATA: | 436 | case I2400M_PT_DATA: |
341 | d_printf(3, dev, "RX: data payload %zu bytes\n", pl_size); | 437 | d_printf(3, dev, "RX: data payload %zu bytes\n", pl_size); |
342 | i2400m_net_rx(i2400m, skb_rx, single, payload, pl_size); | 438 | i2400m_net_rx(i2400m, skb_rx, single_last, payload, pl_size); |
343 | break; | 439 | break; |
344 | case I2400M_PT_CTRL: | 440 | case I2400M_PT_CTRL: |
345 | i2400m_rx_ctl(i2400m, skb_rx, payload, pl_size); | 441 | i2400m_rx_ctl(i2400m, skb_rx, payload, pl_size); |
@@ -347,6 +443,10 @@ void i2400m_rx_payload(struct i2400m *i2400m, struct sk_buff *skb_rx, | |||
347 | case I2400M_PT_TRACE: | 443 | case I2400M_PT_TRACE: |
348 | i2400m_rx_trace(i2400m, payload, pl_size); | 444 | i2400m_rx_trace(i2400m, payload, pl_size); |
349 | break; | 445 | break; |
446 | case I2400M_PT_EDATA: | ||
447 | d_printf(3, dev, "ERX: data payload %zu bytes\n", pl_size); | ||
448 | i2400m_rx_edata(i2400m, skb_rx, single_last, payload, pl_size); | ||
449 | break; | ||
350 | default: /* Anything else shouldn't come to the host */ | 450 | default: /* Anything else shouldn't come to the host */ |
351 | if (printk_ratelimit()) | 451 | if (printk_ratelimit()) |
352 | dev_err(dev, "RX: HW BUG? unexpected payload type %u\n", | 452 | dev_err(dev, "RX: HW BUG? unexpected payload type %u\n", |
@@ -474,7 +574,7 @@ int i2400m_rx(struct i2400m *i2400m, struct sk_buff *skb) | |||
474 | const struct i2400m_msg_hdr *msg_hdr; | 574 | const struct i2400m_msg_hdr *msg_hdr; |
475 | size_t pl_itr, pl_size, skb_len; | 575 | size_t pl_itr, pl_size, skb_len; |
476 | unsigned long flags; | 576 | unsigned long flags; |
477 | unsigned num_pls; | 577 | unsigned num_pls, single_last; |
478 | 578 | ||
479 | skb_len = skb->len; | 579 | skb_len = skb->len; |
480 | d_fnstart(4, dev, "(i2400m %p skb %p [size %zu])\n", | 580 | d_fnstart(4, dev, "(i2400m %p skb %p [size %zu])\n", |
@@ -503,7 +603,8 @@ int i2400m_rx(struct i2400m *i2400m, struct sk_buff *skb) | |||
503 | pl_itr, skb->len); | 603 | pl_itr, skb->len); |
504 | if (result < 0) | 604 | if (result < 0) |
505 | goto error_pl_descr_check; | 605 | goto error_pl_descr_check; |
506 | i2400m_rx_payload(i2400m, skb, num_pls == 1, &msg_hdr->pld[i], | 606 | single_last = num_pls == 1 || i == num_pls - 1; |
607 | i2400m_rx_payload(i2400m, skb, single_last, &msg_hdr->pld[i], | ||
507 | skb->data + pl_itr); | 608 | skb->data + pl_itr); |
508 | pl_itr += ALIGN(pl_size, I2400M_PL_PAD); | 609 | pl_itr += ALIGN(pl_size, I2400M_PL_PAD); |
509 | cond_resched(); /* Don't monopolize */ | 610 | cond_resched(); /* Don't monopolize */ |