Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

net/usb: Ethernet quirks for the LG-VL600 4G modem

This adds a driver for the CDC Ethernet part of this modem. The
device's ID is blacklisted in cdc_ether.c and is white-listed in
this new driver because of the quirks needed to make it useful.
The modem's firmware exposes a CDC ACM port for modem control and a
CDC Ethernet port for network data. The descriptors look fine but
both ports actually are some sort of multiplexers requiring non-
standard headers added/removed from every packet or they get
ignored. All information is based on a usb traffic log from a
Windows machine.

On the Verizon 4G network I've seen speeds up to 1.1MB/s so far with
this driver, a speed-o-meter site reports 16.2Mbps/10.5Mbps.
Userspace scripts are required to talk to the CDC ACM port.

Signed-off-by: Andrzej Zaborowski <balrogg@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>

authored by

Andrzej Zaborowski and committed by
David S. Miller
7a635ea9 d005a09e

+387 -9
+15
drivers/net/usb/Kconfig
··· 433 433 To compile this driver as a module, choose M here: the 434 434 module will be called sierra_net. 435 435 436 + config USB_VL600 437 + tristate "LG VL600 modem dongle" 438 + depends on USB_NET_CDCETHER 439 + select USB_ACM 440 + help 441 + Select this if you want to use an LG Electronics 4G/LTE usb modem 442 + called VL600. This driver only handles the ethernet 443 + interface exposed by the modem firmware. To establish a connection 444 + you will first need a userspace program that sends the right 445 + command to the modem through its CDC ACM port, and most 446 + likely also a DHCP client. See this thread about using the 447 + 4G modem from Verizon: 448 + 449 + http://ubuntuforums.org/showpost.php?p=10589647&postcount=17 450 + 436 451 endmenu
+1
drivers/net/usb/Makefile
··· 27 27 obj-$(CONFIG_USB_SIERRA_NET) += sierra_net.o 28 28 obj-$(CONFIG_USB_NET_CX82310_ETH) += cx82310_eth.o 29 29 obj-$(CONFIG_USB_NET_CDC_NCM) += cdc_ncm.o 30 + obj-$(CONFIG_USB_VL600) += lg-vl600.o 30 31
+15 -6
drivers/net/usb/cdc_ether.c
··· 378 378 __le32_to_cpu(speeds[1]) / 1000); 379 379 } 380 380 381 - static void cdc_status(struct usbnet *dev, struct urb *urb) 381 + void usbnet_cdc_status(struct usbnet *dev, struct urb *urb) 382 382 { 383 383 struct usb_cdc_notification *event; 384 384 ··· 418 418 break; 419 419 } 420 420 } 421 + EXPORT_SYMBOL_GPL(usbnet_cdc_status); 421 422 422 - static int cdc_bind(struct usbnet *dev, struct usb_interface *intf) 423 + int usbnet_cdc_bind(struct usbnet *dev, struct usb_interface *intf) 423 424 { 424 425 int status; 425 426 struct cdc_state *info = (void *) &dev->data; ··· 442 441 */ 443 442 return 0; 444 443 } 444 + EXPORT_SYMBOL_GPL(usbnet_cdc_bind); 445 445 446 446 static int cdc_manage_power(struct usbnet *dev, int on) 447 447 { ··· 454 452 .description = "CDC Ethernet Device", 455 453 .flags = FLAG_ETHER, 456 454 // .check_connect = cdc_check_connect, 457 - .bind = cdc_bind, 455 + .bind = usbnet_cdc_bind, 458 456 .unbind = usbnet_cdc_unbind, 459 - .status = cdc_status, 457 + .status = usbnet_cdc_status, 460 458 .manage_power = cdc_manage_power, 461 459 }; 462 460 463 461 static const struct driver_info mbm_info = { 464 462 .description = "Mobile Broadband Network Device", 465 463 .flags = FLAG_WWAN, 466 - .bind = cdc_bind, 464 + .bind = usbnet_cdc_bind, 467 465 .unbind = usbnet_cdc_unbind, 468 - .status = cdc_status, 466 + .status = usbnet_cdc_status, 469 467 .manage_power = cdc_manage_power, 470 468 }; 471 469 ··· 559 557 .idVendor = 0x07B4, 560 558 .idProduct = 0x0F02, /* R-1000 */ 561 559 ZAURUS_MASTER_INTERFACE, 560 + .driver_info = 0, 561 + }, 562 + 563 + /* LG Electronics VL600 wants additional headers on every frame */ 564 + { 565 + USB_DEVICE_AND_INTERFACE_INFO(0x1004, 0x61aa, USB_CLASS_COMM, 566 + USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE), 562 567 .driver_info = 0, 563 568 }, 564 569
+346
drivers/net/usb/lg-vl600.c
··· 1 + /* 2 + * Ethernet interface part of the LG VL600 LTE modem (4G dongle) 3 + * 4 + * Copyright (C) 2011 Intel Corporation 5 + * Author: Andrzej Zaborowski <balrogg@gmail.com> 6 + * 7 + * This program is free software; you can redistribute it and/or modify 8 + * it under the terms of the GNU General Public License as published by 9 + * the Free Software Foundation; either version 2 of the License, or 10 + * (at your option) any later version. 11 + * 12 + * This program is distributed in the hope that it will be useful, 13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 + * GNU General Public License for more details. 16 + * 17 + * You should have received a copy of the GNU General Public License 18 + * along with this program; if not, write to the Free Software 19 + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 + */ 21 + #include <linux/etherdevice.h> 22 + #include <linux/ethtool.h> 23 + #include <linux/mii.h> 24 + #include <linux/usb.h> 25 + #include <linux/usb/cdc.h> 26 + #include <linux/usb/usbnet.h> 27 + #include <linux/if_ether.h> 28 + #include <linux/if_arp.h> 29 + #include <linux/inetdevice.h> 30 + 31 + /* 32 + * The device has a CDC ACM port for modem control (it claims to be 33 + * CDC ACM anyway) and a CDC Ethernet port for actual network data. 34 + * It will however ignore data on both ports that is not encapsulated 35 + * in a specific way, any data returned is also encapsulated the same 36 + * way. The headers don't seem to follow any popular standard. 37 + * 38 + * This driver adds and strips these headers from the ethernet frames 39 + * sent/received from the CDC Ethernet port. The proprietary header 40 + * replaces the standard ethernet header in a packet so only actual 41 + * ethernet frames are allowed. The headers allow some form of 42 + * multiplexing by using non standard values of the .h_proto field. 43 + * Windows/Mac drivers do send a couple of such frames to the device 44 + * during initialisation, with protocol set to 0x0906 or 0x0b06 and (what 45 + * seems to be) a flag in the .dummy_flags. This doesn't seem necessary 46 + * for modem operation but can possibly be used for GPS or other funcitons. 47 + */ 48 + 49 + struct vl600_frame_hdr { 50 + __le32 len; 51 + __le32 serial; 52 + __le32 pkt_cnt; 53 + __le32 dummy_flags; 54 + __le32 dummy; 55 + __le32 magic; 56 + } __attribute__((packed)); 57 + 58 + struct vl600_pkt_hdr { 59 + __le32 dummy[2]; 60 + __le32 len; 61 + __be16 h_proto; 62 + } __attribute__((packed)); 63 + 64 + struct vl600_state { 65 + struct sk_buff *current_rx_buf; 66 + }; 67 + 68 + static int vl600_bind(struct usbnet *dev, struct usb_interface *intf) 69 + { 70 + int ret; 71 + struct vl600_state *s = kzalloc(sizeof(struct vl600_state), GFP_KERNEL); 72 + 73 + if (!s) 74 + return -ENOMEM; 75 + 76 + ret = usbnet_cdc_bind(dev, intf); 77 + if (ret) { 78 + kfree(s); 79 + return ret; 80 + } 81 + 82 + dev->driver_priv = s; 83 + 84 + /* ARP packets don't go through, but they're also of no use. The 85 + * subnet has only two hosts anyway: us and the gateway / DHCP 86 + * server (probably simulated by modem firmware or network operator) 87 + * whose address changes everytime we connect to the intarwebz and 88 + * who doesn't bother answering ARP requests either. So hardware 89 + * addresses have no meaning, the destination and the source of every 90 + * packet depend only on whether it is on the IN or OUT endpoint. */ 91 + dev->net->flags |= IFF_NOARP; 92 + 93 + return ret; 94 + } 95 + 96 + static void vl600_unbind(struct usbnet *dev, struct usb_interface *intf) 97 + { 98 + struct vl600_state *s = dev->driver_priv; 99 + 100 + if (s->current_rx_buf) 101 + dev_kfree_skb(s->current_rx_buf); 102 + 103 + kfree(s); 104 + 105 + return usbnet_cdc_unbind(dev, intf); 106 + } 107 + 108 + static int vl600_rx_fixup(struct usbnet *dev, struct sk_buff *skb) 109 + { 110 + struct vl600_frame_hdr *frame; 111 + struct vl600_pkt_hdr *packet; 112 + struct ethhdr *ethhdr; 113 + int packet_len, count; 114 + struct sk_buff *buf = skb; 115 + struct sk_buff *clone; 116 + struct vl600_state *s = dev->driver_priv; 117 + 118 + /* Frame lengths are generally 4B multiplies but every couple of 119 + * hours there's an odd number of bytes sized yet correct frame, 120 + * so don't require this. */ 121 + 122 + /* Allow a packet (or multiple packets batched together) to be 123 + * split across many frames. We don't allow a new batch to 124 + * begin in the same frame another one is ending however, and no 125 + * leading or trailing pad bytes. */ 126 + if (s->current_rx_buf) { 127 + frame = (struct vl600_frame_hdr *) s->current_rx_buf->data; 128 + if (skb->len + s->current_rx_buf->len > 129 + le32_to_cpup(&frame->len)) { 130 + netif_err(dev, ifup, dev->net, "Fragment too long\n"); 131 + dev->net->stats.rx_length_errors++; 132 + goto error; 133 + } 134 + 135 + buf = s->current_rx_buf; 136 + memcpy(skb_put(buf, skb->len), skb->data, skb->len); 137 + } else if (skb->len < 4) { 138 + netif_err(dev, ifup, dev->net, "Frame too short\n"); 139 + dev->net->stats.rx_length_errors++; 140 + goto error; 141 + } 142 + 143 + frame = (struct vl600_frame_hdr *) buf->data; 144 + /* NOTE: Should check that frame->magic == 0x53544448? 145 + * Otherwise if we receive garbage at the beginning of the frame 146 + * we may end up allocating a huge buffer and saving all the 147 + * future incoming data into it. */ 148 + 149 + if (buf->len < sizeof(*frame) || 150 + buf->len != le32_to_cpup(&frame->len)) { 151 + /* Save this fragment for later assembly */ 152 + if (s->current_rx_buf) 153 + return 0; 154 + 155 + s->current_rx_buf = skb_copy_expand(skb, 0, 156 + le32_to_cpup(&frame->len), GFP_ATOMIC); 157 + if (!s->current_rx_buf) { 158 + netif_err(dev, ifup, dev->net, "Reserving %i bytes " 159 + "for packet assembly failed.\n", 160 + le32_to_cpup(&frame->len)); 161 + dev->net->stats.rx_errors++; 162 + } 163 + 164 + return 0; 165 + } 166 + 167 + count = le32_to_cpup(&frame->pkt_cnt); 168 + 169 + skb_pull(buf, sizeof(*frame)); 170 + 171 + while (count--) { 172 + if (buf->len < sizeof(*packet)) { 173 + netif_err(dev, ifup, dev->net, "Packet too short\n"); 174 + goto error; 175 + } 176 + 177 + packet = (struct vl600_pkt_hdr *) buf->data; 178 + packet_len = sizeof(*packet) + le32_to_cpup(&packet->len); 179 + if (packet_len > buf->len) { 180 + netif_err(dev, ifup, dev->net, 181 + "Bad packet length stored in header\n"); 182 + goto error; 183 + } 184 + 185 + /* Packet header is same size as the ethernet header 186 + * (sizeof(*packet) == sizeof(*ethhdr)), additionally 187 + * the h_proto field is in the same place so we just leave it 188 + * alone and fill in the remaining fields. 189 + */ 190 + ethhdr = (struct ethhdr *) skb->data; 191 + if (be16_to_cpup(&ethhdr->h_proto) == ETH_P_ARP && 192 + buf->len > 0x26) { 193 + /* Copy the addresses from packet contents */ 194 + memcpy(ethhdr->h_source, 195 + &buf->data[sizeof(*ethhdr) + 0x8], 196 + ETH_ALEN); 197 + memcpy(ethhdr->h_dest, 198 + &buf->data[sizeof(*ethhdr) + 0x12], 199 + ETH_ALEN); 200 + } else { 201 + memset(ethhdr->h_source, 0, ETH_ALEN); 202 + memcpy(ethhdr->h_dest, dev->net->dev_addr, ETH_ALEN); 203 + } 204 + 205 + if (count) { 206 + /* Not the last packet in this batch */ 207 + clone = skb_clone(buf, GFP_ATOMIC); 208 + if (!clone) 209 + goto error; 210 + 211 + skb_trim(clone, packet_len); 212 + usbnet_skb_return(dev, clone); 213 + 214 + skb_pull(buf, (packet_len + 3) & ~3); 215 + } else { 216 + skb_trim(buf, packet_len); 217 + 218 + if (s->current_rx_buf) { 219 + usbnet_skb_return(dev, buf); 220 + s->current_rx_buf = NULL; 221 + return 0; 222 + } 223 + 224 + return 1; 225 + } 226 + } 227 + 228 + error: 229 + if (s->current_rx_buf) { 230 + dev_kfree_skb_any(s->current_rx_buf); 231 + s->current_rx_buf = NULL; 232 + } 233 + dev->net->stats.rx_errors++; 234 + return 0; 235 + } 236 + 237 + static struct sk_buff *vl600_tx_fixup(struct usbnet *dev, 238 + struct sk_buff *skb, gfp_t flags) 239 + { 240 + struct sk_buff *ret; 241 + struct vl600_frame_hdr *frame; 242 + struct vl600_pkt_hdr *packet; 243 + static uint32_t serial = 1; 244 + int orig_len = skb->len - sizeof(struct ethhdr); 245 + int full_len = (skb->len + sizeof(struct vl600_frame_hdr) + 3) & ~3; 246 + 247 + frame = (struct vl600_frame_hdr *) skb->data; 248 + if (skb->len > sizeof(*frame) && skb->len == le32_to_cpup(&frame->len)) 249 + return skb; /* Already encapsulated? */ 250 + 251 + if (skb->len < sizeof(struct ethhdr)) 252 + /* Drop, device can only deal with ethernet packets */ 253 + return NULL; 254 + 255 + if (!skb_cloned(skb)) { 256 + int headroom = skb_headroom(skb); 257 + int tailroom = skb_tailroom(skb); 258 + 259 + if (tailroom >= full_len - skb->len - sizeof(*frame) && 260 + headroom >= sizeof(*frame)) 261 + /* There's enough head and tail room */ 262 + goto encapsulate; 263 + 264 + if (headroom + tailroom + skb->len >= full_len) { 265 + /* There's enough total room, just readjust */ 266 + skb->data = memmove(skb->head + sizeof(*frame), 267 + skb->data, skb->len); 268 + skb_set_tail_pointer(skb, skb->len); 269 + goto encapsulate; 270 + } 271 + } 272 + 273 + /* Alloc a new skb with the required size */ 274 + ret = skb_copy_expand(skb, sizeof(struct vl600_frame_hdr), full_len - 275 + skb->len - sizeof(struct vl600_frame_hdr), flags); 276 + dev_kfree_skb_any(skb); 277 + if (!ret) 278 + return ret; 279 + skb = ret; 280 + 281 + encapsulate: 282 + /* Packet header is same size as ethernet packet header 283 + * (sizeof(*packet) == sizeof(struct ethhdr)), additionally the 284 + * h_proto field is in the same place so we just leave it alone and 285 + * overwrite the remaining fields. 286 + */ 287 + packet = (struct vl600_pkt_hdr *) skb->data; 288 + memset(&packet->dummy, 0, sizeof(packet->dummy)); 289 + packet->len = cpu_to_le32(orig_len); 290 + 291 + frame = (struct vl600_frame_hdr *) skb_push(skb, sizeof(*frame)); 292 + memset(frame, 0, sizeof(*frame)); 293 + frame->len = cpu_to_le32(full_len); 294 + frame->serial = cpu_to_le32(serial++); 295 + frame->pkt_cnt = cpu_to_le32(1); 296 + 297 + if (skb->len < full_len) /* Pad */ 298 + skb_put(skb, full_len - skb->len); 299 + 300 + return skb; 301 + } 302 + 303 + static const struct driver_info vl600_info = { 304 + .description = "LG VL600 modem", 305 + .flags = FLAG_ETHER | FLAG_RX_ASSEMBLE, 306 + .bind = vl600_bind, 307 + .unbind = vl600_unbind, 308 + .status = usbnet_cdc_status, 309 + .rx_fixup = vl600_rx_fixup, 310 + .tx_fixup = vl600_tx_fixup, 311 + }; 312 + 313 + static const struct usb_device_id products[] = { 314 + { 315 + USB_DEVICE_AND_INTERFACE_INFO(0x1004, 0x61aa, USB_CLASS_COMM, 316 + USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE), 317 + .driver_info = (unsigned long) &vl600_info, 318 + }, 319 + {}, /* End */ 320 + }; 321 + MODULE_DEVICE_TABLE(usb, products); 322 + 323 + static struct usb_driver lg_vl600_driver = { 324 + .name = "lg-vl600", 325 + .id_table = products, 326 + .probe = usbnet_probe, 327 + .disconnect = usbnet_disconnect, 328 + .suspend = usbnet_suspend, 329 + .resume = usbnet_resume, 330 + }; 331 + 332 + static int __init vl600_init(void) 333 + { 334 + return usb_register(&lg_vl600_driver); 335 + } 336 + module_init(vl600_init); 337 + 338 + static void __exit vl600_exit(void) 339 + { 340 + usb_deregister(&lg_vl600_driver); 341 + } 342 + module_exit(vl600_exit); 343 + 344 + MODULE_AUTHOR("Anrzej Zaborowski"); 345 + MODULE_DESCRIPTION("LG-VL600 modem's ethernet link"); 346 + MODULE_LICENSE("GPL");
+7 -3
drivers/net/usb/usbnet.c
··· 387 387 static inline void rx_process (struct usbnet *dev, struct sk_buff *skb) 388 388 { 389 389 if (dev->driver_info->rx_fixup && 390 - !dev->driver_info->rx_fixup (dev, skb)) 391 - goto error; 390 + !dev->driver_info->rx_fixup (dev, skb)) { 391 + /* With RX_ASSEMBLE, rx_fixup() must update counters */ 392 + if (!(dev->driver_info->flags & FLAG_RX_ASSEMBLE)) 393 + dev->net->stats.rx_errors++; 394 + goto done; 395 + } 392 396 // else network stack removes extra byte if we forced a short packet 393 397 394 398 if (skb->len) { ··· 405 401 } 406 402 407 403 netif_dbg(dev, rx_err, dev->net, "drop\n"); 408 - error: 409 404 dev->net->stats.rx_errors++; 405 + done: 410 406 skb_queue_tail(&dev->done, skb); 411 407 } 412 408
+3
include/linux/usb/usbnet.h
··· 102 102 * Affects statistic (counters) and short packet handling. 103 103 */ 104 104 #define FLAG_MULTI_PACKET 0x1000 105 + #define FLAG_RX_ASSEMBLE 0x2000 /* rx packets may span >1 frames */ 105 106 106 107 /* init device ... can sleep, or cause probe() failure */ 107 108 int (*bind)(struct usbnet *, struct usb_interface *); ··· 173 172 }; 174 173 175 174 extern int usbnet_generic_cdc_bind(struct usbnet *, struct usb_interface *); 175 + extern int usbnet_cdc_bind(struct usbnet *, struct usb_interface *); 176 176 extern void usbnet_cdc_unbind(struct usbnet *, struct usb_interface *); 177 + extern void usbnet_cdc_status(struct usbnet *, struct urb *); 177 178 178 179 /* CDC and RNDIS support the same host-chosen packet filters for IN transfers */ 179 180 #define DEFAULT_FILTER (USB_CDC_PACKET_TYPE_BROADCAST \