162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Ethernet interface part of the LG VL600 LTE modem (4G dongle) 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2011 Intel Corporation 662306a36Sopenharmony_ci * Author: Andrzej Zaborowski <balrogg@gmail.com> 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci#include <linux/etherdevice.h> 962306a36Sopenharmony_ci#include <linux/ethtool.h> 1062306a36Sopenharmony_ci#include <linux/mii.h> 1162306a36Sopenharmony_ci#include <linux/usb.h> 1262306a36Sopenharmony_ci#include <linux/usb/cdc.h> 1362306a36Sopenharmony_ci#include <linux/usb/usbnet.h> 1462306a36Sopenharmony_ci#include <linux/if_ether.h> 1562306a36Sopenharmony_ci#include <linux/if_arp.h> 1662306a36Sopenharmony_ci#include <linux/inetdevice.h> 1762306a36Sopenharmony_ci#include <linux/module.h> 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci/* 2062306a36Sopenharmony_ci * The device has a CDC ACM port for modem control (it claims to be 2162306a36Sopenharmony_ci * CDC ACM anyway) and a CDC Ethernet port for actual network data. 2262306a36Sopenharmony_ci * It will however ignore data on both ports that is not encapsulated 2362306a36Sopenharmony_ci * in a specific way, any data returned is also encapsulated the same 2462306a36Sopenharmony_ci * way. The headers don't seem to follow any popular standard. 2562306a36Sopenharmony_ci * 2662306a36Sopenharmony_ci * This driver adds and strips these headers from the ethernet frames 2762306a36Sopenharmony_ci * sent/received from the CDC Ethernet port. The proprietary header 2862306a36Sopenharmony_ci * replaces the standard ethernet header in a packet so only actual 2962306a36Sopenharmony_ci * ethernet frames are allowed. The headers allow some form of 3062306a36Sopenharmony_ci * multiplexing by using non standard values of the .h_proto field. 3162306a36Sopenharmony_ci * Windows/Mac drivers do send a couple of such frames to the device 3262306a36Sopenharmony_ci * during initialisation, with protocol set to 0x0906 or 0x0b06 and (what 3362306a36Sopenharmony_ci * seems to be) a flag in the .dummy_flags. This doesn't seem necessary 3462306a36Sopenharmony_ci * for modem operation but can possibly be used for GPS or other functions. 3562306a36Sopenharmony_ci */ 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_cistruct vl600_frame_hdr { 3862306a36Sopenharmony_ci __le32 len; 3962306a36Sopenharmony_ci __le32 serial; 4062306a36Sopenharmony_ci __le32 pkt_cnt; 4162306a36Sopenharmony_ci __le32 dummy_flags; 4262306a36Sopenharmony_ci __le32 dummy; 4362306a36Sopenharmony_ci __le32 magic; 4462306a36Sopenharmony_ci} __attribute__((packed)); 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_cistruct vl600_pkt_hdr { 4762306a36Sopenharmony_ci __le32 dummy[2]; 4862306a36Sopenharmony_ci __le32 len; 4962306a36Sopenharmony_ci __be16 h_proto; 5062306a36Sopenharmony_ci} __attribute__((packed)); 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_cistruct vl600_state { 5362306a36Sopenharmony_ci struct sk_buff *current_rx_buf; 5462306a36Sopenharmony_ci}; 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_cistatic int vl600_bind(struct usbnet *dev, struct usb_interface *intf) 5762306a36Sopenharmony_ci{ 5862306a36Sopenharmony_ci int ret; 5962306a36Sopenharmony_ci struct vl600_state *s = kzalloc(sizeof(struct vl600_state), GFP_KERNEL); 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci if (!s) 6262306a36Sopenharmony_ci return -ENOMEM; 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci ret = usbnet_cdc_bind(dev, intf); 6562306a36Sopenharmony_ci if (ret) { 6662306a36Sopenharmony_ci kfree(s); 6762306a36Sopenharmony_ci return ret; 6862306a36Sopenharmony_ci } 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci dev->driver_priv = s; 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci /* ARP packets don't go through, but they're also of no use. The 7362306a36Sopenharmony_ci * subnet has only two hosts anyway: us and the gateway / DHCP 7462306a36Sopenharmony_ci * server (probably simulated by modem firmware or network operator) 7562306a36Sopenharmony_ci * whose address changes every time we connect to the intarwebz and 7662306a36Sopenharmony_ci * who doesn't bother answering ARP requests either. So hardware 7762306a36Sopenharmony_ci * addresses have no meaning, the destination and the source of every 7862306a36Sopenharmony_ci * packet depend only on whether it is on the IN or OUT endpoint. */ 7962306a36Sopenharmony_ci dev->net->flags |= IFF_NOARP; 8062306a36Sopenharmony_ci /* IPv6 NDP relies on multicast. Enable it by default. */ 8162306a36Sopenharmony_ci dev->net->flags |= IFF_MULTICAST; 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci return ret; 8462306a36Sopenharmony_ci} 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_cistatic void vl600_unbind(struct usbnet *dev, struct usb_interface *intf) 8762306a36Sopenharmony_ci{ 8862306a36Sopenharmony_ci struct vl600_state *s = dev->driver_priv; 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci dev_kfree_skb(s->current_rx_buf); 9162306a36Sopenharmony_ci kfree(s); 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci return usbnet_cdc_unbind(dev, intf); 9462306a36Sopenharmony_ci} 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_cistatic int vl600_rx_fixup(struct usbnet *dev, struct sk_buff *skb) 9762306a36Sopenharmony_ci{ 9862306a36Sopenharmony_ci struct vl600_frame_hdr *frame; 9962306a36Sopenharmony_ci struct vl600_pkt_hdr *packet; 10062306a36Sopenharmony_ci struct ethhdr *ethhdr; 10162306a36Sopenharmony_ci int packet_len, count; 10262306a36Sopenharmony_ci struct sk_buff *buf = skb; 10362306a36Sopenharmony_ci struct sk_buff *clone; 10462306a36Sopenharmony_ci struct vl600_state *s = dev->driver_priv; 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci /* Frame lengths are generally 4B multiplies but every couple of 10762306a36Sopenharmony_ci * hours there's an odd number of bytes sized yet correct frame, 10862306a36Sopenharmony_ci * so don't require this. */ 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci /* Allow a packet (or multiple packets batched together) to be 11162306a36Sopenharmony_ci * split across many frames. We don't allow a new batch to 11262306a36Sopenharmony_ci * begin in the same frame another one is ending however, and no 11362306a36Sopenharmony_ci * leading or trailing pad bytes. */ 11462306a36Sopenharmony_ci if (s->current_rx_buf) { 11562306a36Sopenharmony_ci frame = (struct vl600_frame_hdr *) s->current_rx_buf->data; 11662306a36Sopenharmony_ci if (skb->len + s->current_rx_buf->len > 11762306a36Sopenharmony_ci le32_to_cpup(&frame->len)) { 11862306a36Sopenharmony_ci netif_err(dev, ifup, dev->net, "Fragment too long\n"); 11962306a36Sopenharmony_ci dev->net->stats.rx_length_errors++; 12062306a36Sopenharmony_ci goto error; 12162306a36Sopenharmony_ci } 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci buf = s->current_rx_buf; 12462306a36Sopenharmony_ci skb_put_data(buf, skb->data, skb->len); 12562306a36Sopenharmony_ci } else if (skb->len < 4) { 12662306a36Sopenharmony_ci netif_err(dev, ifup, dev->net, "Frame too short\n"); 12762306a36Sopenharmony_ci dev->net->stats.rx_length_errors++; 12862306a36Sopenharmony_ci goto error; 12962306a36Sopenharmony_ci } 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci frame = (struct vl600_frame_hdr *) buf->data; 13262306a36Sopenharmony_ci /* Yes, check that frame->magic == 0x53544448 (or 0x44544d48), 13362306a36Sopenharmony_ci * otherwise we may run out of memory w/a bad packet */ 13462306a36Sopenharmony_ci if (ntohl(frame->magic) != 0x53544448 && 13562306a36Sopenharmony_ci ntohl(frame->magic) != 0x44544d48) 13662306a36Sopenharmony_ci goto error; 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci if (buf->len < sizeof(*frame) || 13962306a36Sopenharmony_ci buf->len != le32_to_cpup(&frame->len)) { 14062306a36Sopenharmony_ci /* Save this fragment for later assembly */ 14162306a36Sopenharmony_ci if (s->current_rx_buf) 14262306a36Sopenharmony_ci return 0; 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci s->current_rx_buf = skb_copy_expand(skb, 0, 14562306a36Sopenharmony_ci le32_to_cpup(&frame->len), GFP_ATOMIC); 14662306a36Sopenharmony_ci if (!s->current_rx_buf) 14762306a36Sopenharmony_ci dev->net->stats.rx_errors++; 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci return 0; 15062306a36Sopenharmony_ci } 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci count = le32_to_cpup(&frame->pkt_cnt); 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci skb_pull(buf, sizeof(*frame)); 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci while (count--) { 15762306a36Sopenharmony_ci if (buf->len < sizeof(*packet)) { 15862306a36Sopenharmony_ci netif_err(dev, ifup, dev->net, "Packet too short\n"); 15962306a36Sopenharmony_ci goto error; 16062306a36Sopenharmony_ci } 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci packet = (struct vl600_pkt_hdr *) buf->data; 16362306a36Sopenharmony_ci packet_len = sizeof(*packet) + le32_to_cpup(&packet->len); 16462306a36Sopenharmony_ci if (packet_len > buf->len) { 16562306a36Sopenharmony_ci netif_err(dev, ifup, dev->net, 16662306a36Sopenharmony_ci "Bad packet length stored in header\n"); 16762306a36Sopenharmony_ci goto error; 16862306a36Sopenharmony_ci } 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci /* Packet header is same size as the ethernet header 17162306a36Sopenharmony_ci * (sizeof(*packet) == sizeof(*ethhdr)), additionally 17262306a36Sopenharmony_ci * the h_proto field is in the same place so we just leave it 17362306a36Sopenharmony_ci * alone and fill in the remaining fields. 17462306a36Sopenharmony_ci */ 17562306a36Sopenharmony_ci ethhdr = (struct ethhdr *) skb->data; 17662306a36Sopenharmony_ci if (be16_to_cpup(ðhdr->h_proto) == ETH_P_ARP && 17762306a36Sopenharmony_ci buf->len > 0x26) { 17862306a36Sopenharmony_ci /* Copy the addresses from packet contents */ 17962306a36Sopenharmony_ci memcpy(ethhdr->h_source, 18062306a36Sopenharmony_ci &buf->data[sizeof(*ethhdr) + 0x8], 18162306a36Sopenharmony_ci ETH_ALEN); 18262306a36Sopenharmony_ci memcpy(ethhdr->h_dest, 18362306a36Sopenharmony_ci &buf->data[sizeof(*ethhdr) + 0x12], 18462306a36Sopenharmony_ci ETH_ALEN); 18562306a36Sopenharmony_ci } else { 18662306a36Sopenharmony_ci eth_zero_addr(ethhdr->h_source); 18762306a36Sopenharmony_ci memcpy(ethhdr->h_dest, dev->net->dev_addr, ETH_ALEN); 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci /* Inbound IPv6 packets have an IPv4 ethertype (0x800) 19062306a36Sopenharmony_ci * for some reason. Peek at the L3 header to check 19162306a36Sopenharmony_ci * for IPv6 packets, and set the ethertype to IPv6 19262306a36Sopenharmony_ci * (0x86dd) so Linux can understand it. 19362306a36Sopenharmony_ci */ 19462306a36Sopenharmony_ci if ((buf->data[sizeof(*ethhdr)] & 0xf0) == 0x60) 19562306a36Sopenharmony_ci ethhdr->h_proto = htons(ETH_P_IPV6); 19662306a36Sopenharmony_ci } 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci if (count) { 19962306a36Sopenharmony_ci /* Not the last packet in this batch */ 20062306a36Sopenharmony_ci clone = skb_clone(buf, GFP_ATOMIC); 20162306a36Sopenharmony_ci if (!clone) 20262306a36Sopenharmony_ci goto error; 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_ci skb_trim(clone, packet_len); 20562306a36Sopenharmony_ci usbnet_skb_return(dev, clone); 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci skb_pull(buf, (packet_len + 3) & ~3); 20862306a36Sopenharmony_ci } else { 20962306a36Sopenharmony_ci skb_trim(buf, packet_len); 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci if (s->current_rx_buf) { 21262306a36Sopenharmony_ci usbnet_skb_return(dev, buf); 21362306a36Sopenharmony_ci s->current_rx_buf = NULL; 21462306a36Sopenharmony_ci return 0; 21562306a36Sopenharmony_ci } 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci return 1; 21862306a36Sopenharmony_ci } 21962306a36Sopenharmony_ci } 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_cierror: 22262306a36Sopenharmony_ci if (s->current_rx_buf) { 22362306a36Sopenharmony_ci dev_kfree_skb_any(s->current_rx_buf); 22462306a36Sopenharmony_ci s->current_rx_buf = NULL; 22562306a36Sopenharmony_ci } 22662306a36Sopenharmony_ci dev->net->stats.rx_errors++; 22762306a36Sopenharmony_ci return 0; 22862306a36Sopenharmony_ci} 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_cistatic struct sk_buff *vl600_tx_fixup(struct usbnet *dev, 23162306a36Sopenharmony_ci struct sk_buff *skb, gfp_t flags) 23262306a36Sopenharmony_ci{ 23362306a36Sopenharmony_ci struct sk_buff *ret; 23462306a36Sopenharmony_ci struct vl600_frame_hdr *frame; 23562306a36Sopenharmony_ci struct vl600_pkt_hdr *packet; 23662306a36Sopenharmony_ci static uint32_t serial = 1; 23762306a36Sopenharmony_ci int orig_len = skb->len - sizeof(struct ethhdr); 23862306a36Sopenharmony_ci int full_len = (skb->len + sizeof(struct vl600_frame_hdr) + 3) & ~3; 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_ci frame = (struct vl600_frame_hdr *) skb->data; 24162306a36Sopenharmony_ci if (skb->len > sizeof(*frame) && skb->len == le32_to_cpup(&frame->len)) 24262306a36Sopenharmony_ci return skb; /* Already encapsulated? */ 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci if (skb->len < sizeof(struct ethhdr)) 24562306a36Sopenharmony_ci /* Drop, device can only deal with ethernet packets */ 24662306a36Sopenharmony_ci return NULL; 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_ci if (!skb_cloned(skb)) { 24962306a36Sopenharmony_ci int headroom = skb_headroom(skb); 25062306a36Sopenharmony_ci int tailroom = skb_tailroom(skb); 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_ci if (tailroom >= full_len - skb->len - sizeof(*frame) && 25362306a36Sopenharmony_ci headroom >= sizeof(*frame)) 25462306a36Sopenharmony_ci /* There's enough head and tail room */ 25562306a36Sopenharmony_ci goto encapsulate; 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_ci if (headroom + tailroom + skb->len >= full_len) { 25862306a36Sopenharmony_ci /* There's enough total room, just readjust */ 25962306a36Sopenharmony_ci skb->data = memmove(skb->head + sizeof(*frame), 26062306a36Sopenharmony_ci skb->data, skb->len); 26162306a36Sopenharmony_ci skb_set_tail_pointer(skb, skb->len); 26262306a36Sopenharmony_ci goto encapsulate; 26362306a36Sopenharmony_ci } 26462306a36Sopenharmony_ci } 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_ci /* Alloc a new skb with the required size */ 26762306a36Sopenharmony_ci ret = skb_copy_expand(skb, sizeof(struct vl600_frame_hdr), full_len - 26862306a36Sopenharmony_ci skb->len - sizeof(struct vl600_frame_hdr), flags); 26962306a36Sopenharmony_ci dev_kfree_skb_any(skb); 27062306a36Sopenharmony_ci if (!ret) 27162306a36Sopenharmony_ci return ret; 27262306a36Sopenharmony_ci skb = ret; 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_ciencapsulate: 27562306a36Sopenharmony_ci /* Packet header is same size as ethernet packet header 27662306a36Sopenharmony_ci * (sizeof(*packet) == sizeof(struct ethhdr)), additionally the 27762306a36Sopenharmony_ci * h_proto field is in the same place so we just leave it alone and 27862306a36Sopenharmony_ci * overwrite the remaining fields. 27962306a36Sopenharmony_ci */ 28062306a36Sopenharmony_ci packet = (struct vl600_pkt_hdr *) skb->data; 28162306a36Sopenharmony_ci /* The VL600 wants IPv6 packets to have an IPv4 ethertype 28262306a36Sopenharmony_ci * Since this modem only supports IPv4 and IPv6, just set all 28362306a36Sopenharmony_ci * frames to 0x0800 (ETH_P_IP) 28462306a36Sopenharmony_ci */ 28562306a36Sopenharmony_ci packet->h_proto = htons(ETH_P_IP); 28662306a36Sopenharmony_ci memset(&packet->dummy, 0, sizeof(packet->dummy)); 28762306a36Sopenharmony_ci packet->len = cpu_to_le32(orig_len); 28862306a36Sopenharmony_ci 28962306a36Sopenharmony_ci frame = skb_push(skb, sizeof(*frame)); 29062306a36Sopenharmony_ci memset(frame, 0, sizeof(*frame)); 29162306a36Sopenharmony_ci frame->len = cpu_to_le32(full_len); 29262306a36Sopenharmony_ci frame->serial = cpu_to_le32(serial++); 29362306a36Sopenharmony_ci frame->pkt_cnt = cpu_to_le32(1); 29462306a36Sopenharmony_ci 29562306a36Sopenharmony_ci if (skb->len < full_len) /* Pad */ 29662306a36Sopenharmony_ci skb_put(skb, full_len - skb->len); 29762306a36Sopenharmony_ci 29862306a36Sopenharmony_ci return skb; 29962306a36Sopenharmony_ci} 30062306a36Sopenharmony_ci 30162306a36Sopenharmony_cistatic const struct driver_info vl600_info = { 30262306a36Sopenharmony_ci .description = "LG VL600 modem", 30362306a36Sopenharmony_ci .flags = FLAG_RX_ASSEMBLE | FLAG_WWAN, 30462306a36Sopenharmony_ci .bind = vl600_bind, 30562306a36Sopenharmony_ci .unbind = vl600_unbind, 30662306a36Sopenharmony_ci .status = usbnet_cdc_status, 30762306a36Sopenharmony_ci .rx_fixup = vl600_rx_fixup, 30862306a36Sopenharmony_ci .tx_fixup = vl600_tx_fixup, 30962306a36Sopenharmony_ci}; 31062306a36Sopenharmony_ci 31162306a36Sopenharmony_cistatic const struct usb_device_id products[] = { 31262306a36Sopenharmony_ci { 31362306a36Sopenharmony_ci USB_DEVICE_AND_INTERFACE_INFO(0x1004, 0x61aa, USB_CLASS_COMM, 31462306a36Sopenharmony_ci USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE), 31562306a36Sopenharmony_ci .driver_info = (unsigned long) &vl600_info, 31662306a36Sopenharmony_ci }, 31762306a36Sopenharmony_ci {}, /* End */ 31862306a36Sopenharmony_ci}; 31962306a36Sopenharmony_ciMODULE_DEVICE_TABLE(usb, products); 32062306a36Sopenharmony_ci 32162306a36Sopenharmony_cistatic struct usb_driver lg_vl600_driver = { 32262306a36Sopenharmony_ci .name = "lg-vl600", 32362306a36Sopenharmony_ci .id_table = products, 32462306a36Sopenharmony_ci .probe = usbnet_probe, 32562306a36Sopenharmony_ci .disconnect = usbnet_disconnect, 32662306a36Sopenharmony_ci .suspend = usbnet_suspend, 32762306a36Sopenharmony_ci .resume = usbnet_resume, 32862306a36Sopenharmony_ci .disable_hub_initiated_lpm = 1, 32962306a36Sopenharmony_ci}; 33062306a36Sopenharmony_ci 33162306a36Sopenharmony_cimodule_usb_driver(lg_vl600_driver); 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_ciMODULE_AUTHOR("Anrzej Zaborowski"); 33462306a36Sopenharmony_ciMODULE_DESCRIPTION("LG-VL600 modem's ethernet link"); 33562306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 336