summaryrefslogtreecommitdiff
path: root/drivers/net/usb/cdc_ncm.c
blob: a6d386477376b5a29af4aff1a1ee6eacd4ddbdf2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
/*
 * USB CDC NCM class device driver
 *
 * Copyright (C) 2009-2011	NVIDIA Corporation.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <linux/module.h>
#include <linux/init.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/ethtool.h>
#include <linux/workqueue.h>
#include <linux/mii.h>
#include <linux/crc32.h>
#include <linux/usb.h>
#include <linux/usb/cdc.h>
#include <linux/usb/usbnet.h>
#include <linux/version.h>


/***************************************************
	NCM Class Definition
***************************************************/

/* NCM request code */
enum ncm_control_requests {
	GET_NTB_PARAMETERS 	= 0x80,	/* required */
	GET_NET_ADDRESS 	= 0x81,
	SET_NET_ADDRESS 	= 0x82,
	GET_NTB_FORMAT 		= 0x83,
	SET_NTB_FORMAT 		= 0x84,	/* 0: NTB-16; 1:NTB-32 */
	GET_NTB_INPUT_SIZE 	= 0x85,	/* required */
	SET_NTB_INPUT_SIZE 	= 0x86,	/* required */
	GET_MAX_DATAGRAM_SIZE 	= 0x87,
	SET_MAX_DATAGRAM_SIZE 	= 0x88,
	GET_CRC_MODE 		= 0x89,
	SET_CRC_MODE 		= 0x8A,
};

/* NCM GET_NTB_PARAMETERS Response */
struct ntb_params {
	__le16 wLength;		/* should be 0x1c */
	__le16 bmNtbFormatsSupported;	/* bit0: NTB-16 (must); bit1: NTB-32 */
	__le32 dwNtbInMaxSize;
	__le16 wNdpInDivisor;
	__le16 wNdpInPayloadRemainder;
	__le16 wNdpInAlignment;
	__le16 wReserved1;	/* zero padding */
	__le32 dwNtbOutMaxSize;
	__le16 wNdpOutDivisor;
	__le16 wNdpOutPayloadRemainder;
	__le16 wNdpOutAlignment;
	__le16 wReserved2;	/* zero padding */
} __attribute__ ((packed));

/***************************************************
	NCM Transfer Block Definition
***************************************************/

#define NTH16_SIGNATURE		0x484D434E
#define NTH32_SIGNATURE		0x686D636E
#define NDP16_SIGNATURE		0x304D434E
#define NDP16_SIG_CRC32		0x314D434E
#define NDP32_SIGNATURE		0x306D636E
#define NDP32_SIG_CRC32		0x316D636E

/* NTH16 NCM Transfer Header */
struct nth16_hdr {
	__le32 dwSignature;	/* signature: must be "NCMH" */
	__le16 wHeaderLength;	/* header length: 0x0C */
	__le16 wSequence;	/* sequence number */
	__le16 wBlockLength;	/* size of this NTB in bytes */
	__le16 wFpIndex;	/* offset of the first NDP16 from the byte zero
				   of the NTB: must be multiple of 4 */
} __attribute__ ((packed));

/* NDP16 NCM Datagram Pointer Header */
struct ndp16_hdr {
	__le32 dwSignature;	/* NDP16 signature */
	__le16 wLength;		/* size of this NDP16 in bytes, must be multiple
				   of 4 and must be at least 16 */
	__le16 wNextFpIndex;	/* byte index to the next NDP16 */
} __attribute__ ((packed));

/* NDP16 NCM Datagram Pointer Entry */
struct ndp16_ent {
	__le16 wDatagramIndex;	/* offset from byte 0 of the NTB */
	__le16 wDatagramLength;	/* length */
};

/* NTH32 NCM Transfer Header */
struct nth32_hdr {
	__le32 dwSignature;	/* signature: must be "ncmh" */
	__le16 wHeaderLength;	/* header length: 0x10 */
	__le16 wSequence;	/* sequence number */
	__le32 dwBlockLength;	/* size of this NTB in bytes */
	__le32 dwFpIndex;	/* offset of the first NDP32 from the byte zero
				   of the NTB: must be multiple of 4 */
} __attribute__ ((packed));

/* NDP32 NCM Datagram Pointer Header */
struct ndp32_hdr {
	__le32 dwSignature;
	__le16 wLength;
	__le16 wResvered6;
	__le32 dwNextNdpIndex;
	__le32 dwReserved12;
} __attribute__ ((packed));

/* NDP32 NCM Datagram Pointer Entry */
struct ndp32_ent {
	__le32 dwDatagramIndex;
	__le32 dwDatagramLength;
};

/* used to check in NCM frames */
#define MIN_NDP16_SIZE	(sizeof(struct ndp16_hdr) + sizeof(struct ndp16_ent)*2)
#define MIN_NDP32_SIZE	(sizeof(struct ndp32_hdr) + sizeof(struct ndp32_ent)*2)

/* used to creat out NCM frames */
#define MAX_NDP16_ENTRIES	5
#define MAX_NDP16_SIZE	(sizeof(struct ndp16_hdr) + sizeof(struct ndp16_ent)*6)

/* private driver data for each NCM device */
struct driver_params {
	struct ntb_params ntb_params;
	u8 ntb_format;
	u8 crc_mode;
	u16 max_datagram_size;
	u16 ndp_offset;
	u16 last_rx_seq;
	u16 tx_seq;
};

/***************************************************
	NCM Rx frame fixup
***************************************************/

static int ncm_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
{
	struct nth16_hdr hdr;
	struct ndp16_hdr fph;
	struct ndp16_ent e;
	u8 *head;
	u8 *fp;
	struct driver_params *params = (struct driver_params *)dev->driver_priv;

	head = (u8 *) skb->data;
	memcpy(&hdr, head, sizeof(struct nth16_hdr));
	le32_to_cpus(&hdr.dwSignature);
	le16_to_cpus(&hdr.wHeaderLength);
	le16_to_cpus(&hdr.wSequence);
	le16_to_cpus(&hdr.wBlockLength);
	le16_to_cpus(&hdr.wFpIndex);

	/* check block header */
	if (hdr.dwSignature != NTH16_SIGNATURE) {
		netdev_err(dev->net, "Invalid NCM block signature!\n");
		return 0;
	}

	if (hdr.wHeaderLength != sizeof(struct nth16_hdr)) {
		netdev_err(dev->net, "Invalid NCM block header size!\n");
		return 0;
	}

	if (hdr.wBlockLength != skb->len) {
		netdev_err(dev->net,
		    "NCM block size doesn't match the input size (%u != %u)\n",
		    hdr.wBlockLength, skb->len);
		return 0;
	}

	if (!IS_ALIGNED(hdr.wFpIndex, 4)) {
		netdev_err(dev->net, "wFpIndex is not 4-byte aligned!\n");
		return 0;
	}

	/* missing frame? */
	if ((hdr.wSequence - params->last_rx_seq) > 1)
		netdev_warn(dev->net, "missing frame (seq# %u last seq# %u)\n",
			hdr.wSequence, params->last_rx_seq);
	params->last_rx_seq = hdr.wSequence;

	fp = head + hdr.wFpIndex;
	memcpy(&fph, fp, sizeof(struct ndp16_hdr));
	le32_to_cpus(&fph.dwSignature);
	le16_to_cpus(&fph.wLength);
	le16_to_cpus(&fph.wNextFpIndex);

	/* check frame pointer header */
	if ((params->crc_mode && fph.dwSignature != NDP16_SIG_CRC32) ||
	    (fph.dwSignature != NDP16_SIGNATURE) ||
	    (fph.wLength < MIN_NDP16_SIZE) || !IS_ALIGNED(fph.wLength, 4)) {
		netdev_err(dev->net, "Invalid NCM frame pointer header!\n");
		return 0;
	}

	fp += sizeof(struct ndp16_hdr);
	memcpy(&e, fp, sizeof(struct ndp16_ent));
	le16_to_cpus(&e.wDatagramIndex);
	le16_to_cpus(&e.wDatagramLength);

	while (e.wDatagramLength > 0) {
		unsigned char *frame;
		struct sk_buff *new_skb;

		if (e.wDatagramLength > params->max_datagram_size) {
			netdev_err(dev->net, "Bad frame length: %d\n",
				e.wDatagramLength);
			return 0;
		}

		frame = head + e.wDatagramIndex;

		new_skb = skb_clone(skb, GFP_ATOMIC);
		if (new_skb) {
			new_skb->len = e.wDatagramLength;
			new_skb->data = frame;
			skb_set_tail_pointer(new_skb, e.wDatagramLength);
			usbnet_skb_return(dev, new_skb);
		} else {
			return 0;
		}

		/* next frame */
		fp += sizeof(struct ndp16_ent);
		memcpy(&e, fp, sizeof(struct ndp16_ent));
		le16_to_cpus(&e.wDatagramIndex);
		le16_to_cpus(&e.wDatagramLength);

		/* check sequence end mark */
		if (e.wDatagramIndex == 0)
			break;
	}

	skb_pull(skb, skb->len);

	return 1;
}

/***************************************************
	NCM Tx frame fixup
***************************************************/

static struct sk_buff *ncm_tx_fixup(struct usbnet *dev, struct sk_buff *skb,
				    gfp_t flags)
{
	u32 blk_size;
	u32 padlen;
	u32 dgm_offset;
	u32 dgm_len;
	int headroom = skb_headroom(skb);
	int tailroom = skb_tailroom(skb);
	struct nth16_hdr hdr;
	struct ndp16_hdr fph;
	struct ndp16_ent e;
	struct driver_params *params = (struct driver_params *)dev->driver_priv;

	dgm_offset = params->ndp_offset + 32;
	dgm_len = ALIGN(skb->len, params->ntb_params.wNdpOutDivisor);

	blk_size = dgm_len + dgm_offset;
	if (blk_size > params->ntb_params.dwNtbOutMaxSize) {
		netdev_err(dev->net, "Tx blk size is too big: %d\n", blk_size);
		return NULL;
	}

	padlen = dgm_len - skb->len;

	/* create an NCM transfer block */
	hdr.dwSignature = cpu_to_le32(NTH16_SIGNATURE);
	hdr.wHeaderLength = cpu_to_le16(sizeof(struct nth16_hdr));
	hdr.wSequence = cpu_to_le16(params->tx_seq);
	if ((blk_size % dev->maxpacket) == 0)
		hdr.wBlockLength = cpu_to_le16(blk_size + 1);
	else
		hdr.wBlockLength = cpu_to_le16(blk_size);

	/* FP starts right after the block header */
	hdr.wFpIndex = cpu_to_le16(params->ndp_offset);
	params->tx_seq++;

	fph.dwSignature = cpu_to_le32(NDP16_SIGNATURE);
	/* send only one frame */
	fph.wLength = cpu_to_le16(sizeof(struct ndp16_hdr) +
				  sizeof(struct ndp16_ent) * 2);
	fph.wNextFpIndex = 0;

	/* first frame */
	e.wDatagramIndex = cpu_to_le16(dgm_offset);
	e.wDatagramLength = cpu_to_le16(skb->len);

	if ((!skb_cloned(skb))
	    && ((headroom + tailroom) >= (dgm_offset + padlen))) {
		if ((headroom < dgm_offset) || (tailroom < padlen)) {
			skb->data = memmove(skb->head + dgm_offset, skb->data,
					    skb->len);
			skb_set_tail_pointer(skb, skb->len);
		}
	} else {
		struct sk_buff *skb2;
		skb2 = skb_copy_expand(skb, dgm_offset, padlen, flags);
		dev_kfree_skb_any(skb);
		skb = skb2;
		if (!skb)
			return NULL;
	}

	skb_push(skb, dgm_offset);
	memset(skb->data, 0, dgm_offset);
	memcpy(skb->data, &hdr, sizeof(struct nth16_hdr));
	memcpy(skb->data + params->ndp_offset, &fph, sizeof(struct ndp16_hdr));
	memcpy(skb->data + params->ndp_offset + sizeof(struct ndp16_hdr), &e,
	       sizeof(struct ndp16_ent));
	skb_put(skb, padlen);

	if (skb->len != blk_size) {
		netdev_err(dev->net, "skb->len != blk_size (%d)\n", blk_size);
		return NULL;
	}

	return skb;
}

/***************************************************
	Send NCM control message
***************************************************/

static int send_ctrl_msg(struct usbnet *dev,
			 u8 dir,
			 u8 request, u16 value, u16 index, void *data, int size)
{
	int retval;

	retval = usb_control_msg(dev->udev, dir ?
				 usb_rcvctrlpipe(dev->udev, 0) :
				 usb_sndctrlpipe(dev->udev, 0),
				 request,
				 dir | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
				 value, index, data, size, 1000);

	if (retval != size)
		netdev_err(dev->net, "usb_control_msg error: %d\n", retval);

	return (retval < 0) ? retval : 0;
}

/***************************************************
	Change MTU
***************************************************/

static int ncm_change_mtu(struct net_device *net, int new_mtu)
{
	struct usbnet *dev = netdev_priv(net);
	struct cdc_state *info = (void *)dev->data;
	struct driver_params *params = (struct driver_params *)dev->driver_priv;
	u16 max_datagram_size;
	int hard_mtu = new_mtu + net->hard_header_len;

	netdev_dbg(dev->net, "new_mtu=%d\n", new_mtu);

	if (new_mtu <= 0 || hard_mtu > 16384)
		return -EINVAL;

	if ((hard_mtu % dev->maxpacket) == 0)
		return -EDOM;

	max_datagram_size = cpu_to_le16(hard_mtu);

	/* assuming the device won't allow changing the max datagram size
	 * larger than max NTB in/out size
	 */
	if (send_ctrl_msg(dev, USB_DIR_OUT, SET_MAX_DATAGRAM_SIZE, 0,
			  info->u->bMasterInterface0, &max_datagram_size, 2)) {
		netdev_warn(dev->net,
			"SET_MAX_DATAGRAM_SIZE request failed!\n");
	}

	if (send_ctrl_msg(dev, USB_DIR_IN, GET_MAX_DATAGRAM_SIZE, 0,
			  info->u->bMasterInterface0, &max_datagram_size, 2)) {
		netdev_warn(dev->net,
			"GET_MAX_DATAGRAM_SIZE request failed!\n");
	} else {
		le16_to_cpus(&max_datagram_size);
		netdev_dbg(dev->net,
			"max datagram size: %d\n", max_datagram_size);
	}

	if (max_datagram_size < hard_mtu)
		return -EINVAL;

	/* update the max datagram size in the driver parameters */
	if (params->max_datagram_size < max_datagram_size)
		params->max_datagram_size = max_datagram_size;

	net->mtu = new_mtu;
	dev->hard_mtu = hard_mtu;

	return 0;
}

static const struct net_device_ops ncm_netdev_ops = {
	.ndo_open		= usbnet_open,
	.ndo_stop		= usbnet_stop,
	.ndo_start_xmit		= usbnet_start_xmit,
	.ndo_tx_timeout		= usbnet_tx_timeout,
	.ndo_change_mtu		= ncm_change_mtu,
	.ndo_set_mac_address 	= eth_mac_addr,
	.ndo_validate_addr	= eth_validate_addr,
};

/***************************************************
	CDC NCM Binding
***************************************************/

static int cdc_ncm_bind(struct usbnet *dev, struct usb_interface *intf)
{
	struct cdc_state *info = (void *)&dev->data;
	int retval;
	struct usb_driver *driver = driver_of(intf);
	struct driver_params *params;
	u32 max_ntb_in_size = 0;
	u16 max_datagram_size = 0;
	u16 ntb_format = 0;
	u16 crc_mode = 0;

	retval = usbnet_generic_cdc_bind(dev, intf);
	if (retval < 0)
		return retval;

	/* create private driver data */
	dev->driver_priv = kmalloc(sizeof(struct driver_params), GFP_KERNEL);
	if (dev->driver_priv == NULL) {
		usb_set_intfdata(info->data, NULL);
		usb_driver_release_interface(driver, info->data);
		return -ENOMEM;
	}

	params = (struct driver_params *)dev->driver_priv;
	memset(params, 0, sizeof(struct driver_params));

	/* get NTB parameters */
	if (send_ctrl_msg(dev, USB_DIR_IN, GET_NTB_PARAMETERS, 0,
			  info->u->bMasterInterface0,
			  &params->ntb_params, sizeof(struct ntb_params))) {
		dev_dbg(&intf->dev, "GET_NTB_PARAMETERS request failed!\n");
		goto bad_param;
	} else {
		le16_to_cpus(&params->ntb_params.wLength);
		le16_to_cpus(&params->ntb_params.bmNtbFormatsSupported);
		le16_to_cpus(&params->ntb_params.dwNtbInMaxSize);
		le16_to_cpus(&params->ntb_params.wNdpInDivisor);
		le16_to_cpus(&params->ntb_params.wNdpInPayloadRemainder);
		le16_to_cpus(&params->ntb_params.wNdpInAlignment);
		le16_to_cpus(&params->ntb_params.dwNtbOutMaxSize);
		le16_to_cpus(&params->ntb_params.wNdpOutDivisor);
		le16_to_cpus(&params->ntb_params.wNdpOutPayloadRemainder);
		le16_to_cpus(&params->ntb_params.wNdpOutAlignment);

		dev_dbg(&intf->dev, "bmNtbFormatsSupported: %u\n",
			params->ntb_params.bmNtbFormatsSupported);
		dev_dbg(&intf->dev, "ndwNtbInMaxSize: %u\n",
			params->ntb_params.dwNtbInMaxSize);
		dev_dbg(&intf->dev, "nwNdpInDivisor: %u\n",
			params->ntb_params.wNdpInDivisor);
		dev_dbg(&intf->dev, "nwNdpInPayloadRemainder: %u\n",
			params->ntb_params.wNdpInPayloadRemainder);
		dev_dbg(&intf->dev, "nwNdpInAlignment: %u\n",
			params->ntb_params.wNdpInAlignment);
		dev_dbg(&intf->dev, "ndwNtbOutMaxSize: %u\n",
			params->ntb_params.dwNtbOutMaxSize);
		dev_dbg(&intf->dev, "nwNdpOutDivisor: %u\n",
			params->ntb_params.wNdpOutDivisor);
		dev_dbg(&intf->dev, "nwNdpOutPayloadRemainder: %u\n",
			params->ntb_params.wNdpOutPayloadRemainder);
		dev_dbg(&intf->dev, "nwNdpOutAlignment: %u\n",
			params->ntb_params.wNdpOutAlignment);
	}

	/* NCM spec 6.2.7 */
	if (params->ntb_params.dwNtbInMaxSize < 2048) {
		dev_dbg(&intf->dev, "invalid NtbInMaxSize\n");
		goto bad_param;
	}

	/* get max NTB input size */
	if (send_ctrl_msg(dev, USB_DIR_IN, GET_NTB_INPUT_SIZE, 0,
			  info->u->bMasterInterface0, &max_ntb_in_size, 4)) {
		dev_dbg(&intf->dev, "GET_NTB_INPUT_SIZE request failed!\n");
		goto bad_param;
	} else {
		le32_to_cpus(&max_ntb_in_size);
		dev_dbg(&intf->dev, "max ntb input size: %u\n",
			max_ntb_in_size);
	}

	dev->rx_urb_size = max_ntb_in_size;

	/* --------- the following control requests are optional --------- */

	/* get net address */
	if (send_ctrl_msg(dev, USB_DIR_IN, GET_NET_ADDRESS, 0,
			  info->u->bMasterInterface0, dev->net->dev_addr,
			  ETH_ALEN)) {
		dev_dbg(&intf->dev, "GET_NET_ADDRESS request failed!\n");
	} else {
		dev_dbg(&intf->dev, "HW addr: "MAC_FMT"\n",
			dev->net->dev_addr[0],
			dev->net->dev_addr[1],
			dev->net->dev_addr[2],
			dev->net->dev_addr[3],
			dev->net->dev_addr[4],
			dev->net->dev_addr[5]);
	}

	/* get NTB format */
	if (send_ctrl_msg(dev, USB_DIR_IN, GET_NTB_FORMAT, 0,
			  info->u->bMasterInterface0, &ntb_format, 2)) {
		dev_dbg(&intf->dev, "GET_NTB_FORMAT request failed!\n");
	} else {
		le16_to_cpus(&ntb_format);
		dev_dbg(&intf->dev, "ntb_format: %s\n", (ntb_format) ?
			"NTB32" : "NTB16");
	}

	/* TODO: only support ntb16 for now - will support ntb32 for usb 3.0 */
	if (ntb_format != 0) {
		dev_dbg(&intf->dev, "The ntb32 is not supported!\n");
		goto bad_param;
	}

	/* get max datagram size */
	if (send_ctrl_msg(dev, USB_DIR_IN, GET_MAX_DATAGRAM_SIZE, 0,
			  info->u->bMasterInterface0, &max_datagram_size, 2)) {
		dev_dbg(&intf->dev, "GET_MAX_DATAGRAM_SIZE request failed!\n");
	} else {
		le16_to_cpus(&max_datagram_size);
		dev_dbg(&intf->dev, "max datagram size: %d\n",
			max_datagram_size);
	}

	if (max_datagram_size < ETH_FRAME_LEN)
		goto bad_param;

	le16_to_cpus(&max_datagram_size);
	params->max_datagram_size = max_datagram_size;

	/* get CRC mode */
	if (send_ctrl_msg(dev, USB_DIR_IN, GET_CRC_MODE, 0,
			  info->u->bMasterInterface0, &crc_mode, 2)) {
		dev_dbg(&intf->dev, "GET_CRC_MODE request failed!\n");
	} else {
		le16_to_cpus(&crc_mode);
		dev_dbg(&intf->dev, "crc mode: %d\n", crc_mode);
	}

	/* disable crc mode in case it is enabled */
	if (crc_mode) {
		crc_mode = 0;
		if (send_ctrl_msg(dev, USB_DIR_OUT, SET_CRC_MODE, 0,
				  info->u->bMasterInterface0, &crc_mode, 2)) {
			dev_dbg(&intf->dev, "SET_CRC_MODE request failed!\n");
			goto bad_param;
		}
	}

	params->ntb_format = ntb_format;
	params->crc_mode = crc_mode;
	params->ndp_offset = ALIGN(sizeof(struct nth16_hdr),
				   params->ntb_params.wNdpOutAlignment);

	/* reserve headroom space for NTH and NDP to avoid memmove */
	dev->net->needed_headroom = params->ndp_offset + 32;
	dev->net->needed_tailroom = params->ntb_params.wNdpOutDivisor;
	dev->net->netdev_ops = &ncm_netdev_ops;

	strcpy(dev->net->name, "ncm%d");

	return 0;

bad_param:
	usb_set_intfdata(info->data, NULL);
	usb_driver_release_interface(driver, info->data);
	kfree(dev->driver_priv);
	return -ENODEV;
}

/***************************************************
	CDC NCM Unbind
***************************************************/

static void cdc_ncm_unbind(struct usbnet *dev, struct usb_interface *intf)
{
	usbnet_cdc_unbind(dev, intf);

	/* free private driver data */
	kfree(dev->driver_priv);
}

static int cdc_manage_power(struct usbnet *dev, int on)
{
	dev->intf->needs_remote_wakeup = on;
	return 0;
}

static const struct driver_info ncm_info = {
	.description = "NCM device",
	.flags = FLAG_ETHER,
	.tx_fixup = ncm_tx_fixup,
	.rx_fixup = ncm_rx_fixup,
	.bind = cdc_ncm_bind,
	.unbind = cdc_ncm_unbind,
	.status = usbnet_cdc_status,
	.manage_power =	cdc_manage_power,
};

static const struct driver_info mbm_info = {
	.description = "MBM device",
	.flags = FLAG_WWAN,
	.tx_fixup = ncm_tx_fixup,
	.rx_fixup = ncm_rx_fixup,
	.bind = cdc_ncm_bind,
	.unbind = cdc_ncm_unbind,
	.status = usbnet_cdc_status,
	.manage_power =	cdc_manage_power,
};

static const struct usb_device_id products[] = {
	{
	 /* Ericsson f5521gw */
	 .match_flags = USB_DEVICE_ID_MATCH_INT_INFO
		| USB_DEVICE_ID_MATCH_DEVICE,
	 USB_DEVICE(0x0bdb,0x190d),
	 .driver_info = (unsigned long)&mbm_info,
	 },
	{
	 /* standard NCM class device */
	 USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_NCM,
			    USB_CDC_PROTO_NONE),
	 .driver_info = (unsigned long)&ncm_info,
	 },
	{},
};

MODULE_DEVICE_TABLE(usb, products);

static struct usb_driver ncm_driver = {
	.name = "CDC NCM device",
	.id_table = products,
	.probe = usbnet_probe,
	.disconnect = usbnet_disconnect,
	.suspend = usbnet_suspend,
	.resume = usbnet_resume,
	.reset_resume =	usbnet_resume,
	.supports_autosuspend = 1,
};

static int __init ncm_init(void)
{
	return usb_register(&ncm_driver);
}

module_init(ncm_init);

static void __exit ncm_exit(void)
{
	usb_deregister(&ncm_driver);
}

module_exit(ncm_exit);

MODULE_AUTHOR("Steve Lin");
MODULE_DESCRIPTION("USB NCM devices");
MODULE_LICENSE("GPL");