Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-or-later
0002 /*
0003  * USB network interface driver for Samsung Kalmia based LTE USB modem like the
0004  * Samsung GT-B3730 and GT-B3710.
0005  *
0006  * Copyright (C) 2011 Marius Bjoernstad Kotsbak <marius@kotsbak.com>
0007  *
0008  * Sponsored by Quicklink Video Distribution Services Ltd.
0009  *
0010  * Based on the cdc_eem module.
0011  */
0012 
0013 #include <linux/module.h>
0014 #include <linux/netdevice.h>
0015 #include <linux/etherdevice.h>
0016 #include <linux/ctype.h>
0017 #include <linux/ethtool.h>
0018 #include <linux/workqueue.h>
0019 #include <linux/mii.h>
0020 #include <linux/usb.h>
0021 #include <linux/crc32.h>
0022 #include <linux/usb/cdc.h>
0023 #include <linux/usb/usbnet.h>
0024 #include <linux/gfp.h>
0025 
0026 /*
0027  * The Samsung Kalmia based LTE USB modems have a CDC ACM port for modem control
0028  * handled by the "option" module and an ethernet data port handled by this
0029  * module.
0030  *
0031  * The stick must first be switched into modem mode by usb_modeswitch
0032  * or similar tool. Then the modem gets sent two initialization packets by
0033  * this module, which gives the MAC address of the device. User space can then
0034  * connect the modem using AT commands through the ACM port and then use
0035  * DHCP on the network interface exposed by this module. Network packets are
0036  * sent to and from the modem in a proprietary format discovered after watching
0037  * the behavior of the windows driver for the modem.
0038  *
0039  * More information about the use of the modem is available in usb_modeswitch
0040  * forum and the project page:
0041  *
0042  * http://www.draisberghof.de/usb_modeswitch/bb/viewtopic.php?t=465
0043  * https://github.com/mkotsbak/Samsung-GT-B3730-linux-driver
0044  */
0045 
0046 /* #define  DEBUG */
0047 /* #define  VERBOSE */
0048 
0049 #define KALMIA_HEADER_LENGTH 6
0050 #define KALMIA_ALIGN_SIZE 4
0051 #define KALMIA_USB_TIMEOUT 10000
0052 
0053 /*-------------------------------------------------------------------------*/
0054 
0055 static int
0056 kalmia_send_init_packet(struct usbnet *dev, u8 *init_msg, u8 init_msg_len,
0057     u8 *buffer, u8 expected_len)
0058 {
0059     int act_len;
0060     int status;
0061 
0062     netdev_dbg(dev->net, "Sending init packet");
0063 
0064     status = usb_bulk_msg(dev->udev, usb_sndbulkpipe(dev->udev, 0x02),
0065         init_msg, init_msg_len, &act_len, KALMIA_USB_TIMEOUT);
0066     if (status != 0) {
0067         netdev_err(dev->net,
0068             "Error sending init packet. Status %i, length %i\n",
0069             status, act_len);
0070         return status;
0071     }
0072     else if (act_len != init_msg_len) {
0073         netdev_err(dev->net,
0074             "Did not send all of init packet. Bytes sent: %i",
0075             act_len);
0076     }
0077     else {
0078         netdev_dbg(dev->net, "Successfully sent init packet.");
0079     }
0080 
0081     status = usb_bulk_msg(dev->udev, usb_rcvbulkpipe(dev->udev, 0x81),
0082         buffer, expected_len, &act_len, KALMIA_USB_TIMEOUT);
0083 
0084     if (status != 0)
0085         netdev_err(dev->net,
0086             "Error receiving init result. Status %i, length %i\n",
0087             status, act_len);
0088     else if (act_len != expected_len)
0089         netdev_err(dev->net, "Unexpected init result length: %i\n",
0090             act_len);
0091 
0092     return status;
0093 }
0094 
0095 static int
0096 kalmia_init_and_get_ethernet_addr(struct usbnet *dev, u8 *ethernet_addr)
0097 {
0098     static const char init_msg_1[] =
0099         { 0x57, 0x50, 0x04, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00,
0100         0x00, 0x00 };
0101     static const char init_msg_2[] =
0102         { 0x57, 0x50, 0x04, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0xf4,
0103         0x00, 0x00 };
0104     static const int buflen = 28;
0105     char *usb_buf;
0106     int status;
0107 
0108     usb_buf = kmalloc(buflen, GFP_DMA | GFP_KERNEL);
0109     if (!usb_buf)
0110         return -ENOMEM;
0111 
0112     memcpy(usb_buf, init_msg_1, 12);
0113     status = kalmia_send_init_packet(dev, usb_buf, ARRAY_SIZE(init_msg_1),
0114                      usb_buf, 24);
0115     if (status != 0)
0116         goto out;
0117 
0118     memcpy(usb_buf, init_msg_2, 12);
0119     status = kalmia_send_init_packet(dev, usb_buf, ARRAY_SIZE(init_msg_2),
0120                      usb_buf, 28);
0121     if (status != 0)
0122         goto out;
0123 
0124     memcpy(ethernet_addr, usb_buf + 10, ETH_ALEN);
0125 out:
0126     kfree(usb_buf);
0127     return status;
0128 }
0129 
0130 static int
0131 kalmia_bind(struct usbnet *dev, struct usb_interface *intf)
0132 {
0133     int status;
0134     u8 ethernet_addr[ETH_ALEN];
0135 
0136     /* Don't bind to AT command interface */
0137     if (intf->cur_altsetting->desc.bInterfaceClass != USB_CLASS_VENDOR_SPEC)
0138         return -EINVAL;
0139 
0140     dev->in = usb_rcvbulkpipe(dev->udev, 0x81 & USB_ENDPOINT_NUMBER_MASK);
0141     dev->out = usb_sndbulkpipe(dev->udev, 0x02 & USB_ENDPOINT_NUMBER_MASK);
0142     dev->status = NULL;
0143 
0144     dev->net->hard_header_len += KALMIA_HEADER_LENGTH;
0145     dev->hard_mtu = 1400;
0146     dev->rx_urb_size = dev->hard_mtu * 10; // Found as optimal after testing
0147 
0148     status = kalmia_init_and_get_ethernet_addr(dev, ethernet_addr);
0149     if (status)
0150         return status;
0151 
0152     eth_hw_addr_set(dev->net, ethernet_addr);
0153 
0154     return status;
0155 }
0156 
0157 static struct sk_buff *
0158 kalmia_tx_fixup(struct usbnet *dev, struct sk_buff *skb, gfp_t flags)
0159 {
0160     struct sk_buff *skb2 = NULL;
0161     u16 content_len;
0162     unsigned char *header_start;
0163     unsigned char ether_type_1, ether_type_2;
0164     u8 remainder, padlen = 0;
0165 
0166     if (!skb_cloned(skb)) {
0167         int headroom = skb_headroom(skb);
0168         int tailroom = skb_tailroom(skb);
0169 
0170         if ((tailroom >= KALMIA_ALIGN_SIZE) && (headroom
0171             >= KALMIA_HEADER_LENGTH))
0172             goto done;
0173 
0174         if ((headroom + tailroom) > (KALMIA_HEADER_LENGTH
0175             + KALMIA_ALIGN_SIZE)) {
0176             skb->data = memmove(skb->head + KALMIA_HEADER_LENGTH,
0177                 skb->data, skb->len);
0178             skb_set_tail_pointer(skb, skb->len);
0179             goto done;
0180         }
0181     }
0182 
0183     skb2 = skb_copy_expand(skb, KALMIA_HEADER_LENGTH,
0184         KALMIA_ALIGN_SIZE, flags);
0185     if (!skb2)
0186         return NULL;
0187 
0188     dev_kfree_skb_any(skb);
0189     skb = skb2;
0190 
0191 done:
0192     header_start = skb_push(skb, KALMIA_HEADER_LENGTH);
0193     ether_type_1 = header_start[KALMIA_HEADER_LENGTH + 12];
0194     ether_type_2 = header_start[KALMIA_HEADER_LENGTH + 13];
0195 
0196     netdev_dbg(dev->net, "Sending etherType: %02x%02x", ether_type_1,
0197         ether_type_2);
0198 
0199     /* According to empiric data for data packages */
0200     header_start[0] = 0x57;
0201     header_start[1] = 0x44;
0202     content_len = skb->len - KALMIA_HEADER_LENGTH;
0203 
0204     put_unaligned_le16(content_len, &header_start[2]);
0205     header_start[4] = ether_type_1;
0206     header_start[5] = ether_type_2;
0207 
0208     /* Align to 4 bytes by padding with zeros */
0209     remainder = skb->len % KALMIA_ALIGN_SIZE;
0210     if (remainder > 0) {
0211         padlen = KALMIA_ALIGN_SIZE - remainder;
0212         skb_put_zero(skb, padlen);
0213     }
0214 
0215     netdev_dbg(dev->net,
0216         "Sending package with length %i and padding %i. Header: %6phC.",
0217         content_len, padlen, header_start);
0218 
0219     return skb;
0220 }
0221 
0222 static int
0223 kalmia_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
0224 {
0225     /*
0226      * Our task here is to strip off framing, leaving skb with one
0227      * data frame for the usbnet framework code to process.
0228      */
0229     static const u8 HEADER_END_OF_USB_PACKET[] =
0230         { 0x57, 0x5a, 0x00, 0x00, 0x08, 0x00 };
0231     static const u8 EXPECTED_UNKNOWN_HEADER_1[] =
0232         { 0x57, 0x43, 0x1e, 0x00, 0x15, 0x02 };
0233     static const u8 EXPECTED_UNKNOWN_HEADER_2[] =
0234         { 0x57, 0x50, 0x0e, 0x00, 0x00, 0x00 };
0235     int i = 0;
0236 
0237     /* incomplete header? */
0238     if (skb->len < KALMIA_HEADER_LENGTH)
0239         return 0;
0240 
0241     do {
0242         struct sk_buff *skb2 = NULL;
0243         u8 *header_start;
0244         u16 usb_packet_length, ether_packet_length;
0245         int is_last;
0246 
0247         header_start = skb->data;
0248 
0249         if (unlikely(header_start[0] != 0x57 || header_start[1] != 0x44)) {
0250             if (!memcmp(header_start, EXPECTED_UNKNOWN_HEADER_1,
0251                 sizeof(EXPECTED_UNKNOWN_HEADER_1)) || !memcmp(
0252                 header_start, EXPECTED_UNKNOWN_HEADER_2,
0253                 sizeof(EXPECTED_UNKNOWN_HEADER_2))) {
0254                 netdev_dbg(dev->net,
0255                     "Received expected unknown frame header: %6phC. Package length: %i\n",
0256                     header_start,
0257                     skb->len - KALMIA_HEADER_LENGTH);
0258             }
0259             else {
0260                 netdev_err(dev->net,
0261                     "Received unknown frame header: %6phC. Package length: %i\n",
0262                     header_start,
0263                     skb->len - KALMIA_HEADER_LENGTH);
0264                 return 0;
0265             }
0266         }
0267         else
0268             netdev_dbg(dev->net,
0269                 "Received header: %6phC. Package length: %i\n",
0270                 header_start, skb->len - KALMIA_HEADER_LENGTH);
0271 
0272         /* subtract start header and end header */
0273         usb_packet_length = skb->len - (2 * KALMIA_HEADER_LENGTH);
0274         ether_packet_length = get_unaligned_le16(&header_start[2]);
0275         skb_pull(skb, KALMIA_HEADER_LENGTH);
0276 
0277         /* Some small packets misses end marker */
0278         if (usb_packet_length < ether_packet_length) {
0279             ether_packet_length = usb_packet_length
0280                 + KALMIA_HEADER_LENGTH;
0281             is_last = true;
0282         }
0283         else {
0284             netdev_dbg(dev->net, "Correct package length #%i", i
0285                 + 1);
0286 
0287             is_last = (memcmp(skb->data + ether_packet_length,
0288                 HEADER_END_OF_USB_PACKET,
0289                 sizeof(HEADER_END_OF_USB_PACKET)) == 0);
0290             if (!is_last) {
0291                 header_start = skb->data + ether_packet_length;
0292                 netdev_dbg(dev->net,
0293                     "End header: %6phC. Package length: %i\n",
0294                     header_start,
0295                     skb->len - KALMIA_HEADER_LENGTH);
0296             }
0297         }
0298 
0299         if (is_last) {
0300             skb2 = skb;
0301         }
0302         else {
0303             skb2 = skb_clone(skb, GFP_ATOMIC);
0304             if (unlikely(!skb2))
0305                 return 0;
0306         }
0307 
0308         skb_trim(skb2, ether_packet_length);
0309 
0310         if (is_last) {
0311             return 1;
0312         }
0313         else {
0314             usbnet_skb_return(dev, skb2);
0315             skb_pull(skb, ether_packet_length);
0316         }
0317 
0318         i++;
0319     }
0320     while (skb->len);
0321 
0322     return 1;
0323 }
0324 
0325 static const struct driver_info kalmia_info = {
0326     .description = "Samsung Kalmia LTE USB dongle",
0327     .flags = FLAG_WWAN,
0328     .bind = kalmia_bind,
0329     .rx_fixup = kalmia_rx_fixup,
0330     .tx_fixup = kalmia_tx_fixup
0331 };
0332 
0333 /*-------------------------------------------------------------------------*/
0334 
0335 static const struct usb_device_id products[] = {
0336     /* The unswitched USB ID, to get the module auto loaded: */
0337     { USB_DEVICE(0x04e8, 0x689a) },
0338     /* The stick switched into modem (by e.g. usb_modeswitch): */
0339     { USB_DEVICE(0x04e8, 0x6889),
0340         .driver_info = (unsigned long) &kalmia_info, },
0341     { /* EMPTY == end of list */} };
0342 MODULE_DEVICE_TABLE( usb, products);
0343 
0344 static struct usb_driver kalmia_driver = {
0345     .name = "kalmia",
0346     .id_table = products,
0347     .probe = usbnet_probe,
0348     .disconnect = usbnet_disconnect,
0349     .suspend = usbnet_suspend,
0350     .resume = usbnet_resume,
0351     .disable_hub_initiated_lpm = 1,
0352 };
0353 
0354 module_usb_driver(kalmia_driver);
0355 
0356 MODULE_AUTHOR("Marius Bjoernstad Kotsbak <marius@kotsbak.com>");
0357 MODULE_DESCRIPTION("Samsung Kalmia USB network driver");
0358 MODULE_LICENSE("GPL");