root/drivers/net/usb/kalmia.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. kalmia_send_init_packet
  2. kalmia_init_and_get_ethernet_addr
  3. kalmia_bind
  4. kalmia_tx_fixup
  5. kalmia_rx_fixup

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

/* [<][>][^][v][top][bottom][index][help] */