Commit | Line | Data |
---|---|---|
15c9ac0c SB |
1 | /* |
2 | * Copyright (C) ST-Ericsson AB 2010 | |
3 | * Author: Sjur Brendeland/sjur.brandeland@stericsson.com | |
4 | * License terms: GNU General Public License (GPL) version 2 | |
5 | */ | |
6 | ||
7 | #include <linux/string.h> | |
8 | #include <linux/skbuff.h> | |
9 | #include <linux/hardirq.h> | |
10 | #include <net/caif/cfpkt.h> | |
11 | ||
12 | #define PKT_PREFIX CAIF_NEEDED_HEADROOM | |
13 | #define PKT_POSTFIX CAIF_NEEDED_TAILROOM | |
14 | #define PKT_LEN_WHEN_EXTENDING 128 | |
15 | #define PKT_ERROR(pkt, errmsg) do { \ | |
16 | cfpkt_priv(pkt)->erronous = true; \ | |
17 | skb_reset_tail_pointer(&pkt->skb); \ | |
18 | pr_warning("CAIF: " errmsg);\ | |
19 | } while (0) | |
20 | ||
21 | struct cfpktq { | |
22 | struct sk_buff_head head; | |
23 | atomic_t count; | |
24 | /* Lock protects count updates */ | |
25 | spinlock_t lock; | |
26 | }; | |
27 | ||
28 | /* | |
29 | * net/caif/ is generic and does not | |
30 | * understand SKB, so we do this typecast | |
31 | */ | |
32 | struct cfpkt { | |
33 | struct sk_buff skb; | |
34 | }; | |
35 | ||
36 | /* Private data inside SKB */ | |
37 | struct cfpkt_priv_data { | |
38 | struct dev_info dev_info; | |
39 | bool erronous; | |
40 | }; | |
41 | ||
42 | inline struct cfpkt_priv_data *cfpkt_priv(struct cfpkt *pkt) | |
43 | { | |
44 | return (struct cfpkt_priv_data *) pkt->skb.cb; | |
45 | } | |
46 | ||
47 | inline bool is_erronous(struct cfpkt *pkt) | |
48 | { | |
49 | return cfpkt_priv(pkt)->erronous; | |
50 | } | |
51 | ||
52 | inline struct sk_buff *pkt_to_skb(struct cfpkt *pkt) | |
53 | { | |
54 | return &pkt->skb; | |
55 | } | |
56 | ||
57 | inline struct cfpkt *skb_to_pkt(struct sk_buff *skb) | |
58 | { | |
59 | return (struct cfpkt *) skb; | |
60 | } | |
61 | ||
62 | ||
63 | struct cfpkt *cfpkt_fromnative(enum caif_direction dir, void *nativepkt) | |
64 | { | |
65 | struct cfpkt *pkt = skb_to_pkt(nativepkt); | |
66 | cfpkt_priv(pkt)->erronous = false; | |
67 | return pkt; | |
68 | } | |
69 | EXPORT_SYMBOL(cfpkt_fromnative); | |
70 | ||
71 | void *cfpkt_tonative(struct cfpkt *pkt) | |
72 | { | |
73 | return (void *) pkt; | |
74 | } | |
75 | EXPORT_SYMBOL(cfpkt_tonative); | |
76 | ||
77 | static struct cfpkt *cfpkt_create_pfx(u16 len, u16 pfx) | |
78 | { | |
79 | struct sk_buff *skb; | |
80 | ||
81 | if (likely(in_interrupt())) | |
82 | skb = alloc_skb(len + pfx, GFP_ATOMIC); | |
83 | else | |
84 | skb = alloc_skb(len + pfx, GFP_KERNEL); | |
85 | ||
86 | if (unlikely(skb == NULL)) | |
87 | return NULL; | |
88 | ||
89 | skb_reserve(skb, pfx); | |
90 | return skb_to_pkt(skb); | |
91 | } | |
92 | ||
93 | inline struct cfpkt *cfpkt_create(u16 len) | |
94 | { | |
95 | return cfpkt_create_pfx(len + PKT_POSTFIX, PKT_PREFIX); | |
96 | } | |
97 | EXPORT_SYMBOL(cfpkt_create); | |
98 | ||
99 | void cfpkt_destroy(struct cfpkt *pkt) | |
100 | { | |
101 | struct sk_buff *skb = pkt_to_skb(pkt); | |
102 | kfree_skb(skb); | |
103 | } | |
104 | EXPORT_SYMBOL(cfpkt_destroy); | |
105 | ||
106 | inline bool cfpkt_more(struct cfpkt *pkt) | |
107 | { | |
108 | struct sk_buff *skb = pkt_to_skb(pkt); | |
109 | return skb->len > 0; | |
110 | } | |
111 | EXPORT_SYMBOL(cfpkt_more); | |
112 | ||
113 | int cfpkt_peek_head(struct cfpkt *pkt, void *data, u16 len) | |
114 | { | |
115 | struct sk_buff *skb = pkt_to_skb(pkt); | |
116 | if (skb_headlen(skb) >= len) { | |
117 | memcpy(data, skb->data, len); | |
118 | return 0; | |
119 | } | |
120 | return !cfpkt_extr_head(pkt, data, len) && | |
121 | !cfpkt_add_head(pkt, data, len); | |
122 | } | |
123 | EXPORT_SYMBOL(cfpkt_peek_head); | |
124 | ||
125 | int cfpkt_extr_head(struct cfpkt *pkt, void *data, u16 len) | |
126 | { | |
127 | struct sk_buff *skb = pkt_to_skb(pkt); | |
128 | u8 *from; | |
129 | if (unlikely(is_erronous(pkt))) | |
130 | return -EPROTO; | |
131 | ||
132 | if (unlikely(len > skb->len)) { | |
133 | PKT_ERROR(pkt, "cfpkt_extr_head read beyond end of packet\n"); | |
134 | return -EPROTO; | |
135 | } | |
136 | ||
137 | if (unlikely(len > skb_headlen(skb))) { | |
138 | if (unlikely(skb_linearize(skb) != 0)) { | |
139 | PKT_ERROR(pkt, "cfpkt_extr_head linearize failed\n"); | |
140 | return -EPROTO; | |
141 | } | |
142 | } | |
143 | from = skb_pull(skb, len); | |
144 | from -= len; | |
145 | memcpy(data, from, len); | |
146 | return 0; | |
147 | } | |
148 | EXPORT_SYMBOL(cfpkt_extr_head); | |
149 | ||
150 | int cfpkt_extr_trail(struct cfpkt *pkt, void *dta, u16 len) | |
151 | { | |
152 | struct sk_buff *skb = pkt_to_skb(pkt); | |
153 | u8 *data = dta; | |
154 | u8 *from; | |
155 | if (unlikely(is_erronous(pkt))) | |
156 | return -EPROTO; | |
157 | ||
158 | if (unlikely(skb_linearize(skb) != 0)) { | |
159 | PKT_ERROR(pkt, "cfpkt_extr_trail linearize failed\n"); | |
160 | return -EPROTO; | |
161 | } | |
162 | if (unlikely(skb->data + len > skb_tail_pointer(skb))) { | |
163 | PKT_ERROR(pkt, "cfpkt_extr_trail read beyond end of packet\n"); | |
164 | return -EPROTO; | |
165 | } | |
166 | from = skb_tail_pointer(skb) - len; | |
167 | skb_trim(skb, skb->len - len); | |
168 | memcpy(data, from, len); | |
169 | return 0; | |
170 | } | |
171 | EXPORT_SYMBOL(cfpkt_extr_trail); | |
172 | ||
173 | int cfpkt_pad_trail(struct cfpkt *pkt, u16 len) | |
174 | { | |
175 | return cfpkt_add_body(pkt, NULL, len); | |
176 | } | |
177 | EXPORT_SYMBOL(cfpkt_pad_trail); | |
178 | ||
179 | int cfpkt_add_body(struct cfpkt *pkt, const void *data, u16 len) | |
180 | { | |
181 | struct sk_buff *skb = pkt_to_skb(pkt); | |
182 | struct sk_buff *lastskb; | |
183 | u8 *to; | |
184 | u16 addlen = 0; | |
185 | ||
186 | ||
187 | if (unlikely(is_erronous(pkt))) | |
188 | return -EPROTO; | |
189 | ||
190 | lastskb = skb; | |
191 | ||
192 | /* Check whether we need to add space at the tail */ | |
193 | if (unlikely(skb_tailroom(skb) < len)) { | |
194 | if (likely(len < PKT_LEN_WHEN_EXTENDING)) | |
195 | addlen = PKT_LEN_WHEN_EXTENDING; | |
196 | else | |
197 | addlen = len; | |
198 | } | |
199 | ||
200 | /* Check whether we need to change the SKB before writing to the tail */ | |
201 | if (unlikely((addlen > 0) || skb_cloned(skb) || skb_shared(skb))) { | |
202 | ||
203 | /* Make sure data is writable */ | |
204 | if (unlikely(skb_cow_data(skb, addlen, &lastskb) < 0)) { | |
205 | PKT_ERROR(pkt, "cfpkt_add_body: cow failed\n"); | |
206 | return -EPROTO; | |
207 | } | |
208 | /* | |
209 | * Is the SKB non-linear after skb_cow_data()? If so, we are | |
210 | * going to add data to the last SKB, so we need to adjust | |
211 | * lengths of the top SKB. | |
212 | */ | |
213 | if (lastskb != skb) { | |
214 | pr_warning("CAIF: %s(): Packet is non-linear\n", | |
215 | __func__); | |
216 | skb->len += len; | |
217 | skb->data_len += len; | |
218 | } | |
219 | } | |
220 | ||
221 | /* All set to put the last SKB and optionally write data there. */ | |
222 | to = skb_put(lastskb, len); | |
223 | if (likely(data)) | |
224 | memcpy(to, data, len); | |
225 | return 0; | |
226 | } | |
227 | EXPORT_SYMBOL(cfpkt_add_body); | |
228 | ||
229 | inline int cfpkt_addbdy(struct cfpkt *pkt, u8 data) | |
230 | { | |
231 | return cfpkt_add_body(pkt, &data, 1); | |
232 | } | |
233 | EXPORT_SYMBOL(cfpkt_addbdy); | |
234 | ||
235 | int cfpkt_add_head(struct cfpkt *pkt, const void *data2, u16 len) | |
236 | { | |
237 | struct sk_buff *skb = pkt_to_skb(pkt); | |
238 | struct sk_buff *lastskb; | |
239 | u8 *to; | |
240 | const u8 *data = data2; | |
241 | if (unlikely(is_erronous(pkt))) | |
242 | return -EPROTO; | |
243 | if (unlikely(skb_headroom(skb) < len)) { | |
244 | PKT_ERROR(pkt, "cfpkt_add_head: no headroom\n"); | |
245 | return -EPROTO; | |
246 | } | |
247 | ||
248 | /* Make sure data is writable */ | |
249 | if (unlikely(skb_cow_data(skb, 0, &lastskb) < 0)) { | |
250 | PKT_ERROR(pkt, "cfpkt_add_head: cow failed\n"); | |
251 | return -EPROTO; | |
252 | } | |
253 | ||
254 | to = skb_push(skb, len); | |
255 | memcpy(to, data, len); | |
256 | return 0; | |
257 | } | |
258 | EXPORT_SYMBOL(cfpkt_add_head); | |
259 | ||
260 | inline int cfpkt_add_trail(struct cfpkt *pkt, const void *data, u16 len) | |
261 | { | |
262 | return cfpkt_add_body(pkt, data, len); | |
263 | } | |
264 | EXPORT_SYMBOL(cfpkt_add_trail); | |
265 | ||
266 | inline u16 cfpkt_getlen(struct cfpkt *pkt) | |
267 | { | |
268 | struct sk_buff *skb = pkt_to_skb(pkt); | |
269 | return skb->len; | |
270 | } | |
271 | EXPORT_SYMBOL(cfpkt_getlen); | |
272 | ||
273 | inline u16 cfpkt_iterate(struct cfpkt *pkt, | |
274 | u16 (*iter_func)(u16, void *, u16), | |
275 | u16 data) | |
276 | { | |
277 | /* | |
278 | * Don't care about the performance hit of linearizing, | |
279 | * Checksum should not be used on high-speed interfaces anyway. | |
280 | */ | |
281 | if (unlikely(is_erronous(pkt))) | |
282 | return -EPROTO; | |
283 | if (unlikely(skb_linearize(&pkt->skb) != 0)) { | |
284 | PKT_ERROR(pkt, "cfpkt_iterate: linearize failed\n"); | |
285 | return -EPROTO; | |
286 | } | |
287 | return iter_func(data, pkt->skb.data, cfpkt_getlen(pkt)); | |
288 | } | |
289 | EXPORT_SYMBOL(cfpkt_iterate); | |
290 | ||
291 | int cfpkt_setlen(struct cfpkt *pkt, u16 len) | |
292 | { | |
293 | struct sk_buff *skb = pkt_to_skb(pkt); | |
294 | ||
295 | ||
296 | if (unlikely(is_erronous(pkt))) | |
297 | return -EPROTO; | |
298 | ||
299 | if (likely(len <= skb->len)) { | |
300 | if (unlikely(skb->data_len)) | |
301 | ___pskb_trim(skb, len); | |
302 | else | |
303 | skb_trim(skb, len); | |
304 | ||
305 | return cfpkt_getlen(pkt); | |
306 | } | |
307 | ||
308 | /* Need to expand SKB */ | |
309 | if (unlikely(!cfpkt_pad_trail(pkt, len - skb->len))) | |
310 | PKT_ERROR(pkt, "cfpkt_setlen: skb_pad_trail failed\n"); | |
311 | ||
312 | return cfpkt_getlen(pkt); | |
313 | } | |
314 | EXPORT_SYMBOL(cfpkt_setlen); | |
315 | ||
316 | struct cfpkt *cfpkt_create_uplink(const unsigned char *data, unsigned int len) | |
317 | { | |
318 | struct cfpkt *pkt = cfpkt_create_pfx(len + PKT_POSTFIX, PKT_PREFIX); | |
319 | if (unlikely(data != NULL)) | |
320 | cfpkt_add_body(pkt, data, len); | |
321 | return pkt; | |
322 | } | |
323 | EXPORT_SYMBOL(cfpkt_create_uplink); | |
324 | ||
325 | struct cfpkt *cfpkt_append(struct cfpkt *dstpkt, | |
326 | struct cfpkt *addpkt, | |
327 | u16 expectlen) | |
328 | { | |
329 | struct sk_buff *dst = pkt_to_skb(dstpkt); | |
330 | struct sk_buff *add = pkt_to_skb(addpkt); | |
331 | u16 addlen = skb_headlen(add); | |
332 | u16 neededtailspace; | |
333 | struct sk_buff *tmp; | |
334 | u16 dstlen; | |
335 | u16 createlen; | |
336 | if (unlikely(is_erronous(dstpkt) || is_erronous(addpkt))) { | |
337 | cfpkt_destroy(addpkt); | |
338 | return dstpkt; | |
339 | } | |
340 | if (expectlen > addlen) | |
341 | neededtailspace = expectlen; | |
342 | else | |
343 | neededtailspace = addlen; | |
344 | ||
345 | if (dst->tail + neededtailspace > dst->end) { | |
346 | /* Create a dumplicate of 'dst' with more tail space */ | |
347 | dstlen = skb_headlen(dst); | |
348 | createlen = dstlen + neededtailspace; | |
349 | tmp = pkt_to_skb( | |
350 | cfpkt_create(createlen + PKT_PREFIX + PKT_POSTFIX)); | |
351 | if (!tmp) | |
352 | return NULL; | |
353 | skb_set_tail_pointer(tmp, dstlen); | |
354 | tmp->len = dstlen; | |
355 | memcpy(tmp->data, dst->data, dstlen); | |
356 | cfpkt_destroy(dstpkt); | |
357 | dst = tmp; | |
358 | } | |
359 | memcpy(skb_tail_pointer(dst), add->data, skb_headlen(add)); | |
360 | cfpkt_destroy(addpkt); | |
361 | dst->tail += addlen; | |
362 | dst->len += addlen; | |
363 | return skb_to_pkt(dst); | |
364 | } | |
365 | EXPORT_SYMBOL(cfpkt_append); | |
366 | ||
367 | struct cfpkt *cfpkt_split(struct cfpkt *pkt, u16 pos) | |
368 | { | |
369 | struct sk_buff *skb2; | |
370 | struct sk_buff *skb = pkt_to_skb(pkt); | |
371 | u8 *split = skb->data + pos; | |
372 | u16 len2nd = skb_tail_pointer(skb) - split; | |
373 | ||
374 | if (unlikely(is_erronous(pkt))) | |
375 | return NULL; | |
376 | ||
377 | if (skb->data + pos > skb_tail_pointer(skb)) { | |
378 | PKT_ERROR(pkt, | |
379 | "cfpkt_split: trying to split beyond end of packet"); | |
380 | return NULL; | |
381 | } | |
382 | ||
383 | /* Create a new packet for the second part of the data */ | |
384 | skb2 = pkt_to_skb( | |
385 | cfpkt_create_pfx(len2nd + PKT_PREFIX + PKT_POSTFIX, | |
386 | PKT_PREFIX)); | |
387 | ||
388 | if (skb2 == NULL) | |
389 | return NULL; | |
390 | ||
391 | /* Reduce the length of the original packet */ | |
392 | skb_set_tail_pointer(skb, pos); | |
393 | skb->len = pos; | |
394 | ||
395 | memcpy(skb2->data, split, len2nd); | |
396 | skb2->tail += len2nd; | |
397 | skb2->len += len2nd; | |
398 | return skb_to_pkt(skb2); | |
399 | } | |
400 | EXPORT_SYMBOL(cfpkt_split); | |
401 | ||
402 | char *cfpkt_log_pkt(struct cfpkt *pkt, char *buf, int buflen) | |
403 | { | |
404 | struct sk_buff *skb = pkt_to_skb(pkt); | |
405 | char *p = buf; | |
406 | int i; | |
407 | ||
408 | /* | |
409 | * Sanity check buffer length, it needs to be at least as large as | |
410 | * the header info: ~=50+ bytes | |
411 | */ | |
412 | if (buflen < 50) | |
413 | return NULL; | |
414 | ||
415 | snprintf(buf, buflen, "%s: pkt:%p len:%ld(%ld+%ld) {%ld,%ld} data: [", | |
416 | is_erronous(pkt) ? "ERRONOUS-SKB" : | |
417 | (skb->data_len != 0 ? "COMPLEX-SKB" : "SKB"), | |
418 | skb, | |
419 | (long) skb->len, | |
420 | (long) (skb_tail_pointer(skb) - skb->data), | |
421 | (long) skb->data_len, | |
422 | (long) (skb->data - skb->head), | |
423 | (long) (skb_tail_pointer(skb) - skb->head)); | |
424 | p = buf + strlen(buf); | |
425 | ||
426 | for (i = 0; i < skb_tail_pointer(skb) - skb->data && i < 300; i++) { | |
427 | if (p > buf + buflen - 10) { | |
428 | sprintf(p, "..."); | |
429 | p = buf + strlen(buf); | |
430 | break; | |
431 | } | |
432 | sprintf(p, "%02x,", skb->data[i]); | |
433 | p = buf + strlen(buf); | |
434 | } | |
435 | sprintf(p, "]\n"); | |
436 | return buf; | |
437 | } | |
438 | EXPORT_SYMBOL(cfpkt_log_pkt); | |
439 | ||
440 | int cfpkt_raw_append(struct cfpkt *pkt, void **buf, unsigned int buflen) | |
441 | { | |
442 | struct sk_buff *skb = pkt_to_skb(pkt); | |
443 | struct sk_buff *lastskb; | |
444 | ||
445 | caif_assert(buf != NULL); | |
446 | if (unlikely(is_erronous(pkt))) | |
447 | return -EPROTO; | |
448 | /* Make sure SKB is writable */ | |
449 | if (unlikely(skb_cow_data(skb, 0, &lastskb) < 0)) { | |
450 | PKT_ERROR(pkt, "cfpkt_raw_append: skb_cow_data failed\n"); | |
451 | return -EPROTO; | |
452 | } | |
453 | ||
454 | if (unlikely(skb_linearize(skb) != 0)) { | |
455 | PKT_ERROR(pkt, "cfpkt_raw_append: linearize failed\n"); | |
456 | return -EPROTO; | |
457 | } | |
458 | ||
459 | if (unlikely(skb_tailroom(skb) < buflen)) { | |
460 | PKT_ERROR(pkt, "cfpkt_raw_append: buffer too short - failed\n"); | |
461 | return -EPROTO; | |
462 | } | |
463 | ||
464 | *buf = skb_put(skb, buflen); | |
465 | return 1; | |
466 | } | |
467 | EXPORT_SYMBOL(cfpkt_raw_append); | |
468 | ||
469 | int cfpkt_raw_extract(struct cfpkt *pkt, void **buf, unsigned int buflen) | |
470 | { | |
471 | struct sk_buff *skb = pkt_to_skb(pkt); | |
472 | ||
473 | caif_assert(buf != NULL); | |
474 | if (unlikely(is_erronous(pkt))) | |
475 | return -EPROTO; | |
476 | ||
477 | if (unlikely(buflen > skb->len)) { | |
478 | PKT_ERROR(pkt, "cfpkt_raw_extract: buflen too large " | |
479 | "- failed\n"); | |
480 | return -EPROTO; | |
481 | } | |
482 | ||
483 | if (unlikely(buflen > skb_headlen(skb))) { | |
484 | if (unlikely(skb_linearize(skb) != 0)) { | |
485 | PKT_ERROR(pkt, "cfpkt_raw_extract: linearize failed\n"); | |
486 | return -EPROTO; | |
487 | } | |
488 | } | |
489 | ||
490 | *buf = skb->data; | |
491 | skb_pull(skb, buflen); | |
492 | ||
493 | return 1; | |
494 | } | |
495 | EXPORT_SYMBOL(cfpkt_raw_extract); | |
496 | ||
497 | inline bool cfpkt_erroneous(struct cfpkt *pkt) | |
498 | { | |
499 | return cfpkt_priv(pkt)->erronous; | |
500 | } | |
501 | EXPORT_SYMBOL(cfpkt_erroneous); | |
502 | ||
503 | struct cfpktq *cfpktq_create(void) | |
504 | { | |
505 | struct cfpktq *q = kmalloc(sizeof(struct cfpktq), GFP_ATOMIC); | |
506 | if (!q) | |
507 | return NULL; | |
508 | skb_queue_head_init(&q->head); | |
509 | atomic_set(&q->count, 0); | |
510 | spin_lock_init(&q->lock); | |
511 | return q; | |
512 | } | |
513 | EXPORT_SYMBOL(cfpktq_create); | |
514 | ||
515 | void cfpkt_queue(struct cfpktq *pktq, struct cfpkt *pkt, unsigned short prio) | |
516 | { | |
517 | atomic_inc(&pktq->count); | |
518 | spin_lock(&pktq->lock); | |
519 | skb_queue_tail(&pktq->head, pkt_to_skb(pkt)); | |
520 | spin_unlock(&pktq->lock); | |
521 | ||
522 | } | |
523 | EXPORT_SYMBOL(cfpkt_queue); | |
524 | ||
525 | struct cfpkt *cfpkt_qpeek(struct cfpktq *pktq) | |
526 | { | |
527 | struct cfpkt *tmp; | |
528 | spin_lock(&pktq->lock); | |
529 | tmp = skb_to_pkt(skb_peek(&pktq->head)); | |
530 | spin_unlock(&pktq->lock); | |
531 | return tmp; | |
532 | } | |
533 | EXPORT_SYMBOL(cfpkt_qpeek); | |
534 | ||
535 | struct cfpkt *cfpkt_dequeue(struct cfpktq *pktq) | |
536 | { | |
537 | struct cfpkt *pkt; | |
538 | spin_lock(&pktq->lock); | |
539 | pkt = skb_to_pkt(skb_dequeue(&pktq->head)); | |
540 | if (pkt) { | |
541 | atomic_dec(&pktq->count); | |
542 | caif_assert(atomic_read(&pktq->count) >= 0); | |
543 | } | |
544 | spin_unlock(&pktq->lock); | |
545 | return pkt; | |
546 | } | |
547 | EXPORT_SYMBOL(cfpkt_dequeue); | |
548 | ||
549 | int cfpkt_qcount(struct cfpktq *pktq) | |
550 | { | |
551 | return atomic_read(&pktq->count); | |
552 | } | |
553 | EXPORT_SYMBOL(cfpkt_qcount); | |
554 | ||
555 | struct cfpkt *cfpkt_clone_release(struct cfpkt *pkt) | |
556 | { | |
557 | struct cfpkt *clone; | |
558 | clone = skb_to_pkt(skb_clone(pkt_to_skb(pkt), GFP_ATOMIC)); | |
559 | /* Free original packet. */ | |
560 | cfpkt_destroy(pkt); | |
561 | if (!clone) | |
562 | return NULL; | |
563 | return clone; | |
564 | } | |
565 | EXPORT_SYMBOL(cfpkt_clone_release); | |
566 | ||
567 | struct caif_payload_info *cfpkt_info(struct cfpkt *pkt) | |
568 | { | |
569 | return (struct caif_payload_info *)&pkt_to_skb(pkt)->cb; | |
570 | } | |
571 | EXPORT_SYMBOL(cfpkt_info); |