~ubuntu-branches/ubuntu/precise/linux-ti-omap4/precise

« back to all changes in this revision

Viewing changes to drivers/net/usb/kalmia.c

  • Committer: Bazaar Package Importer
  • Author(s): Paolo Pisati
  • Date: 2011-06-29 15:23:51 UTC
  • mfrom: (26.1.1 natty-proposed)
  • Revision ID: james.westby@ubuntu.com-20110629152351-xs96tm303d95rpbk
Tags: 3.0.0-1200.2
* Rebased against 3.0.0-6.7
* BSP from TI based on 3.0.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * USB network interface driver for Samsung Kalmia based LTE USB modem like the
 
3
 * Samsung GT-B3730 and GT-B3710.
 
4
 *
 
5
 * Copyright (C) 2011 Marius Bjoernstad Kotsbak <marius@kotsbak.com>
 
6
 *
 
7
 * Sponsored by Quicklink Video Distribution Services Ltd.
 
8
 *
 
9
 * Based on the cdc_eem module.
 
10
 *
 
11
 * This program is free software; you can redistribute it and/or modify
 
12
 * it under the terms of the GNU General Public License as published by
 
13
 * the Free Software Foundation; either version 2 of the License, or
 
14
 * (at your option) any later version.
 
15
 */
 
16
 
 
17
#include <linux/module.h>
 
18
#include <linux/init.h>
 
19
#include <linux/netdevice.h>
 
20
#include <linux/etherdevice.h>
 
21
#include <linux/ctype.h>
 
22
#include <linux/ethtool.h>
 
23
#include <linux/workqueue.h>
 
24
#include <linux/mii.h>
 
25
#include <linux/usb.h>
 
26
#include <linux/crc32.h>
 
27
#include <linux/usb/cdc.h>
 
28
#include <linux/usb/usbnet.h>
 
29
#include <linux/gfp.h>
 
30
 
 
31
/*
 
32
 * The Samsung Kalmia based LTE USB modems have a CDC ACM port for modem control
 
33
 * handled by the "option" module and an ethernet data port handled by this
 
34
 * module.
 
35
 *
 
36
 * The stick must first be switched into modem mode by usb_modeswitch
 
37
 * or similar tool. Then the modem gets sent two initialization packets by
 
38
 * this module, which gives the MAC address of the device. User space can then
 
39
 * connect the modem using AT commands through the ACM port and then use
 
40
 * DHCP on the network interface exposed by this module. Network packets are
 
41
 * sent to and from the modem in a proprietary format discovered after watching
 
42
 * the behavior of the windows driver for the modem.
 
43
 *
 
44
 * More information about the use of the modem is available in usb_modeswitch
 
45
 * forum and the project page:
 
46
 *
 
47
 * http://www.draisberghof.de/usb_modeswitch/bb/viewtopic.php?t=465
 
48
 * https://github.com/mkotsbak/Samsung-GT-B3730-linux-driver
 
49
 */
 
50
 
 
51
/* #define      DEBUG */
 
52
/* #define      VERBOSE */
 
53
 
 
54
#define KALMIA_HEADER_LENGTH 6
 
55
#define KALMIA_ALIGN_SIZE 4
 
56
#define KALMIA_USB_TIMEOUT 10000
 
57
 
 
58
/*-------------------------------------------------------------------------*/
 
59
 
 
60
static int
 
61
kalmia_send_init_packet(struct usbnet *dev, u8 *init_msg, u8 init_msg_len,
 
62
        u8 *buffer, u8 expected_len)
 
63
{
 
64
        int act_len;
 
65
        int status;
 
66
 
 
67
        netdev_dbg(dev->net, "Sending init packet");
 
68
 
 
69
        status = usb_bulk_msg(dev->udev, usb_sndbulkpipe(dev->udev, 0x02),
 
70
                init_msg, init_msg_len, &act_len, KALMIA_USB_TIMEOUT);
 
71
        if (status != 0) {
 
72
                netdev_err(dev->net,
 
73
                        "Error sending init packet. Status %i, length %i\n",
 
74
                        status, act_len);
 
75
                return status;
 
76
        }
 
77
        else if (act_len != init_msg_len) {
 
78
                netdev_err(dev->net,
 
79
                        "Did not send all of init packet. Bytes sent: %i",
 
80
                        act_len);
 
81
        }
 
82
        else {
 
83
                netdev_dbg(dev->net, "Successfully sent init packet.");
 
84
        }
 
85
 
 
86
        status = usb_bulk_msg(dev->udev, usb_rcvbulkpipe(dev->udev, 0x81),
 
87
                buffer, expected_len, &act_len, KALMIA_USB_TIMEOUT);
 
88
 
 
89
        if (status != 0)
 
90
                netdev_err(dev->net,
 
91
                        "Error receiving init result. Status %i, length %i\n",
 
92
                        status, act_len);
 
93
        else if (act_len != expected_len)
 
94
                netdev_err(dev->net, "Unexpected init result length: %i\n",
 
95
                        act_len);
 
96
 
 
97
        return status;
 
98
}
 
99
 
 
100
static int
 
101
kalmia_init_and_get_ethernet_addr(struct usbnet *dev, u8 *ethernet_addr)
 
102
{
 
103
        const static char init_msg_1[] =
 
104
                { 0x57, 0x50, 0x04, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00,
 
105
                0x00, 0x00 };
 
106
        const static char init_msg_2[] =
 
107
                { 0x57, 0x50, 0x04, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0xf4,
 
108
                0x00, 0x00 };
 
109
        const static int buflen = 28;
 
110
        char *usb_buf;
 
111
        int status;
 
112
 
 
113
        usb_buf = kmalloc(buflen, GFP_DMA | GFP_KERNEL);
 
114
        if (!usb_buf)
 
115
                return -ENOMEM;
 
116
 
 
117
        memcpy(usb_buf, init_msg_1, 12);
 
118
        status = kalmia_send_init_packet(dev, usb_buf, sizeof(init_msg_1)
 
119
                / sizeof(init_msg_1[0]), usb_buf, 24);
 
120
        if (status != 0)
 
121
                return status;
 
122
 
 
123
        memcpy(usb_buf, init_msg_2, 12);
 
124
        status = kalmia_send_init_packet(dev, usb_buf, sizeof(init_msg_2)
 
125
                / sizeof(init_msg_2[0]), usb_buf, 28);
 
126
        if (status != 0)
 
127
                return status;
 
128
 
 
129
        memcpy(ethernet_addr, usb_buf + 10, ETH_ALEN);
 
130
 
 
131
        kfree(usb_buf);
 
132
        return status;
 
133
}
 
134
 
 
135
static int
 
136
kalmia_bind(struct usbnet *dev, struct usb_interface *intf)
 
137
{
 
138
        int status;
 
139
        u8 ethernet_addr[ETH_ALEN];
 
140
 
 
141
        /* Don't bind to AT command interface */
 
142
        if (intf->cur_altsetting->desc.bInterfaceClass != USB_CLASS_VENDOR_SPEC)
 
143
                return -EINVAL;
 
144
 
 
145
        dev->in = usb_rcvbulkpipe(dev->udev, 0x81 & USB_ENDPOINT_NUMBER_MASK);
 
146
        dev->out = usb_sndbulkpipe(dev->udev, 0x02 & USB_ENDPOINT_NUMBER_MASK);
 
147
        dev->status = NULL;
 
148
 
 
149
        dev->net->hard_header_len += KALMIA_HEADER_LENGTH;
 
150
        dev->hard_mtu = 1400;
 
151
        dev->rx_urb_size = dev->hard_mtu * 10; // Found as optimal after testing
 
152
 
 
153
        status = kalmia_init_and_get_ethernet_addr(dev, ethernet_addr);
 
154
 
 
155
        if (status < 0) {
 
156
                usb_set_intfdata(intf, NULL);
 
157
                usb_driver_release_interface(driver_of(intf), intf);
 
158
                return status;
 
159
        }
 
160
 
 
161
        memcpy(dev->net->dev_addr, ethernet_addr, ETH_ALEN);
 
162
        memcpy(dev->net->perm_addr, ethernet_addr, ETH_ALEN);
 
163
 
 
164
        return status;
 
165
}
 
166
 
 
167
static struct sk_buff *
 
168
kalmia_tx_fixup(struct usbnet *dev, struct sk_buff *skb, gfp_t flags)
 
169
{
 
170
        struct sk_buff *skb2 = NULL;
 
171
        u16 content_len;
 
172
        unsigned char *header_start;
 
173
        unsigned char ether_type_1, ether_type_2;
 
174
        u8 remainder, padlen = 0;
 
175
 
 
176
        if (!skb_cloned(skb)) {
 
177
                int headroom = skb_headroom(skb);
 
178
                int tailroom = skb_tailroom(skb);
 
179
 
 
180
                if ((tailroom >= KALMIA_ALIGN_SIZE) && (headroom
 
181
                        >= KALMIA_HEADER_LENGTH))
 
182
                        goto done;
 
183
 
 
184
                if ((headroom + tailroom) > (KALMIA_HEADER_LENGTH
 
185
                        + KALMIA_ALIGN_SIZE)) {
 
186
                        skb->data = memmove(skb->head + KALMIA_HEADER_LENGTH,
 
187
                                skb->data, skb->len);
 
188
                        skb_set_tail_pointer(skb, skb->len);
 
189
                        goto done;
 
190
                }
 
191
        }
 
192
 
 
193
        skb2 = skb_copy_expand(skb, KALMIA_HEADER_LENGTH,
 
194
                KALMIA_ALIGN_SIZE, flags);
 
195
        if (!skb2)
 
196
                return NULL;
 
197
 
 
198
        dev_kfree_skb_any(skb);
 
199
        skb = skb2;
 
200
 
 
201
done:
 
202
        header_start = skb_push(skb, KALMIA_HEADER_LENGTH);
 
203
        ether_type_1 = header_start[KALMIA_HEADER_LENGTH + 12];
 
204
        ether_type_2 = header_start[KALMIA_HEADER_LENGTH + 13];
 
205
 
 
206
        netdev_dbg(dev->net, "Sending etherType: %02x%02x", ether_type_1,
 
207
                ether_type_2);
 
208
 
 
209
        /* According to empiric data for data packages */
 
210
        header_start[0] = 0x57;
 
211
        header_start[1] = 0x44;
 
212
        content_len = skb->len - KALMIA_HEADER_LENGTH;
 
213
 
 
214
        put_unaligned_le16(content_len, &header_start[2]);
 
215
        header_start[4] = ether_type_1;
 
216
        header_start[5] = ether_type_2;
 
217
 
 
218
        /* Align to 4 bytes by padding with zeros */
 
219
        remainder = skb->len % KALMIA_ALIGN_SIZE;
 
220
        if (remainder > 0) {
 
221
                padlen = KALMIA_ALIGN_SIZE - remainder;
 
222
                memset(skb_put(skb, padlen), 0, padlen);
 
223
        }
 
224
 
 
225
        netdev_dbg(
 
226
                dev->net,
 
227
                "Sending package with length %i and padding %i. Header: %02x:%02x:%02x:%02x:%02x:%02x.",
 
228
                content_len, padlen, header_start[0], header_start[1],
 
229
                header_start[2], header_start[3], header_start[4],
 
230
                header_start[5]);
 
231
 
 
232
        return skb;
 
233
}
 
234
 
 
235
static int
 
236
kalmia_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
 
237
{
 
238
        /*
 
239
         * Our task here is to strip off framing, leaving skb with one
 
240
         * data frame for the usbnet framework code to process.
 
241
         */
 
242
        const static u8 HEADER_END_OF_USB_PACKET[] =
 
243
                { 0x57, 0x5a, 0x00, 0x00, 0x08, 0x00 };
 
244
        const static u8 EXPECTED_UNKNOWN_HEADER_1[] =
 
245
                { 0x57, 0x43, 0x1e, 0x00, 0x15, 0x02 };
 
246
        const static u8 EXPECTED_UNKNOWN_HEADER_2[] =
 
247
                { 0x57, 0x50, 0x0e, 0x00, 0x00, 0x00 };
 
248
        int i = 0;
 
249
 
 
250
        /* incomplete header? */
 
251
        if (skb->len < KALMIA_HEADER_LENGTH)
 
252
                return 0;
 
253
 
 
254
        do {
 
255
                struct sk_buff *skb2 = NULL;
 
256
                u8 *header_start;
 
257
                u16 usb_packet_length, ether_packet_length;
 
258
                int is_last;
 
259
 
 
260
                header_start = skb->data;
 
261
 
 
262
                if (unlikely(header_start[0] != 0x57 || header_start[1] != 0x44)) {
 
263
                        if (!memcmp(header_start, EXPECTED_UNKNOWN_HEADER_1,
 
264
                                sizeof(EXPECTED_UNKNOWN_HEADER_1)) || !memcmp(
 
265
                                header_start, EXPECTED_UNKNOWN_HEADER_2,
 
266
                                sizeof(EXPECTED_UNKNOWN_HEADER_2))) {
 
267
                                netdev_dbg(
 
268
                                        dev->net,
 
269
                                        "Received expected unknown frame header: %02x:%02x:%02x:%02x:%02x:%02x. Package length: %i\n",
 
270
                                        header_start[0], header_start[1],
 
271
                                        header_start[2], header_start[3],
 
272
                                        header_start[4], header_start[5],
 
273
                                        skb->len - KALMIA_HEADER_LENGTH);
 
274
                        }
 
275
                        else {
 
276
                                netdev_err(
 
277
                                        dev->net,
 
278
                                        "Received unknown frame header: %02x:%02x:%02x:%02x:%02x:%02x. Package length: %i\n",
 
279
                                        header_start[0], header_start[1],
 
280
                                        header_start[2], header_start[3],
 
281
                                        header_start[4], header_start[5],
 
282
                                        skb->len - KALMIA_HEADER_LENGTH);
 
283
                                return 0;
 
284
                        }
 
285
                }
 
286
                else
 
287
                        netdev_dbg(
 
288
                                dev->net,
 
289
                                "Received header: %02x:%02x:%02x:%02x:%02x:%02x. Package length: %i\n",
 
290
                                header_start[0], header_start[1], header_start[2],
 
291
                                header_start[3], header_start[4], header_start[5],
 
292
                                skb->len - KALMIA_HEADER_LENGTH);
 
293
 
 
294
                /* subtract start header and end header */
 
295
                usb_packet_length = skb->len - (2 * KALMIA_HEADER_LENGTH);
 
296
                ether_packet_length = get_unaligned_le16(&header_start[2]);
 
297
                skb_pull(skb, KALMIA_HEADER_LENGTH);
 
298
 
 
299
                /* Some small packets misses end marker */
 
300
                if (usb_packet_length < ether_packet_length) {
 
301
                        ether_packet_length = usb_packet_length
 
302
                                + KALMIA_HEADER_LENGTH;
 
303
                        is_last = true;
 
304
                }
 
305
                else {
 
306
                        netdev_dbg(dev->net, "Correct package length #%i", i
 
307
                                + 1);
 
308
 
 
309
                        is_last = (memcmp(skb->data + ether_packet_length,
 
310
                                HEADER_END_OF_USB_PACKET,
 
311
                                sizeof(HEADER_END_OF_USB_PACKET)) == 0);
 
312
                        if (!is_last) {
 
313
                                header_start = skb->data + ether_packet_length;
 
314
                                netdev_dbg(
 
315
                                        dev->net,
 
316
                                        "End header: %02x:%02x:%02x:%02x:%02x:%02x. Package length: %i\n",
 
317
                                        header_start[0], header_start[1],
 
318
                                        header_start[2], header_start[3],
 
319
                                        header_start[4], header_start[5],
 
320
                                        skb->len - KALMIA_HEADER_LENGTH);
 
321
                        }
 
322
                }
 
323
 
 
324
                if (is_last) {
 
325
                        skb2 = skb;
 
326
                }
 
327
                else {
 
328
                        skb2 = skb_clone(skb, GFP_ATOMIC);
 
329
                        if (unlikely(!skb2))
 
330
                                return 0;
 
331
                }
 
332
 
 
333
                skb_trim(skb2, ether_packet_length);
 
334
 
 
335
                if (is_last) {
 
336
                        return 1;
 
337
                }
 
338
                else {
 
339
                        usbnet_skb_return(dev, skb2);
 
340
                        skb_pull(skb, ether_packet_length);
 
341
                }
 
342
 
 
343
                i++;
 
344
        }
 
345
        while (skb->len);
 
346
 
 
347
        return 1;
 
348
}
 
349
 
 
350
static const struct driver_info kalmia_info = {
 
351
        .description = "Samsung Kalmia LTE USB dongle",
 
352
        .flags = FLAG_WWAN,
 
353
        .bind = kalmia_bind,
 
354
        .rx_fixup = kalmia_rx_fixup,
 
355
        .tx_fixup = kalmia_tx_fixup
 
356
};
 
357
 
 
358
/*-------------------------------------------------------------------------*/
 
359
 
 
360
static const struct usb_device_id products[] = {
 
361
        /* The unswitched USB ID, to get the module auto loaded: */
 
362
        { USB_DEVICE(0x04e8, 0x689a) },
 
363
        /* The stick swithed into modem (by e.g. usb_modeswitch): */
 
364
        { USB_DEVICE(0x04e8, 0x6889),
 
365
                .driver_info = (unsigned long) &kalmia_info, },
 
366
        { /* EMPTY == end of list */} };
 
367
MODULE_DEVICE_TABLE( usb, products);
 
368
 
 
369
static struct usb_driver kalmia_driver = {
 
370
        .name = "kalmia",
 
371
        .id_table = products,
 
372
        .probe = usbnet_probe,
 
373
        .disconnect = usbnet_disconnect,
 
374
        .suspend = usbnet_suspend,
 
375
        .resume = usbnet_resume
 
376
};
 
377
 
 
378
static int __init kalmia_init(void)
 
379
{
 
380
        return usb_register(&kalmia_driver);
 
381
}
 
382
module_init( kalmia_init);
 
383
 
 
384
static void __exit kalmia_exit(void)
 
385
{
 
386
        usb_deregister(&kalmia_driver);
 
387
}
 
388
module_exit( kalmia_exit);
 
389
 
 
390
MODULE_AUTHOR("Marius Bjoernstad Kotsbak <marius@kotsbak.com>");
 
391
MODULE_DESCRIPTION("Samsung Kalmia USB network driver");
 
392
MODULE_LICENSE("GPL");