162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * USB-to-WWAN Driver for Sierra Wireless modems 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2008, 2009, 2010 Paxton Smith, Matthew Safar, Rory Filer 662306a36Sopenharmony_ci * <linux@sierrawireless.com> 762306a36Sopenharmony_ci * 862306a36Sopenharmony_ci * Portions of this based on the cdc_ether driver by David Brownell (2003-2005) 962306a36Sopenharmony_ci * and Ole Andre Vadla Ravnas (ActiveSync) (2006). 1062306a36Sopenharmony_ci * 1162306a36Sopenharmony_ci * IMPORTANT DISCLAIMER: This driver is not commercially supported by 1262306a36Sopenharmony_ci * Sierra Wireless. Use at your own risk. 1362306a36Sopenharmony_ci */ 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_ci#define DRIVER_VERSION "v.2.0" 1662306a36Sopenharmony_ci#define DRIVER_AUTHOR "Paxton Smith, Matthew Safar, Rory Filer" 1762306a36Sopenharmony_ci#define DRIVER_DESC "USB-to-WWAN Driver for Sierra Wireless modems" 1862306a36Sopenharmony_cistatic const char driver_name[] = "sierra_net"; 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci/* if defined debug messages enabled */ 2162306a36Sopenharmony_ci/*#define DEBUG*/ 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci#include <linux/module.h> 2462306a36Sopenharmony_ci#include <linux/etherdevice.h> 2562306a36Sopenharmony_ci#include <linux/ethtool.h> 2662306a36Sopenharmony_ci#include <linux/mii.h> 2762306a36Sopenharmony_ci#include <linux/sched.h> 2862306a36Sopenharmony_ci#include <linux/timer.h> 2962306a36Sopenharmony_ci#include <linux/usb.h> 3062306a36Sopenharmony_ci#include <linux/usb/cdc.h> 3162306a36Sopenharmony_ci#include <net/ip.h> 3262306a36Sopenharmony_ci#include <net/udp.h> 3362306a36Sopenharmony_ci#include <asm/unaligned.h> 3462306a36Sopenharmony_ci#include <linux/usb/usbnet.h> 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci#define SWI_USB_REQUEST_GET_FW_ATTR 0x06 3762306a36Sopenharmony_ci#define SWI_GET_FW_ATTR_MASK 0x08 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci/* atomic counter partially included in MAC address to make sure 2 devices 4062306a36Sopenharmony_ci * do not end up with the same MAC - concept breaks in case of > 255 ifaces 4162306a36Sopenharmony_ci */ 4262306a36Sopenharmony_cistatic atomic_t iface_counter = ATOMIC_INIT(0); 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ci/* 4562306a36Sopenharmony_ci * SYNC Timer Delay definition used to set the expiry time 4662306a36Sopenharmony_ci */ 4762306a36Sopenharmony_ci#define SIERRA_NET_SYNCDELAY (2*HZ) 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci/* Max. MTU supported. The modem buffers are limited to 1500 */ 5062306a36Sopenharmony_ci#define SIERRA_NET_MAX_SUPPORTED_MTU 1500 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci/* The SIERRA_NET_USBCTL_BUF_LEN defines a buffer size allocated for control 5362306a36Sopenharmony_ci * message reception ... and thus the max. received packet. 5462306a36Sopenharmony_ci * (May be the cause for parse_hip returning -EINVAL) 5562306a36Sopenharmony_ci */ 5662306a36Sopenharmony_ci#define SIERRA_NET_USBCTL_BUF_LEN 1024 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci/* Overriding the default usbnet rx_urb_size */ 5962306a36Sopenharmony_ci#define SIERRA_NET_RX_URB_SIZE (8 * 1024) 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci/* Private data structure */ 6262306a36Sopenharmony_cistruct sierra_net_data { 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci u16 link_up; /* air link up or down */ 6562306a36Sopenharmony_ci u8 tx_hdr_template[4]; /* part of HIP hdr for tx'd packets */ 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci u8 sync_msg[4]; /* SYNC message */ 6862306a36Sopenharmony_ci u8 shdwn_msg[4]; /* Shutdown message */ 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci /* Backpointer to the container */ 7162306a36Sopenharmony_ci struct usbnet *usbnet; 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci u8 ifnum; /* interface number */ 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci/* Bit masks, must be a power of 2 */ 7662306a36Sopenharmony_ci#define SIERRA_NET_EVENT_RESP_AVAIL 0x01 7762306a36Sopenharmony_ci#define SIERRA_NET_TIMER_EXPIRY 0x02 7862306a36Sopenharmony_ci unsigned long kevent_flags; 7962306a36Sopenharmony_ci struct work_struct sierra_net_kevent; 8062306a36Sopenharmony_ci struct timer_list sync_timer; /* For retrying SYNC sequence */ 8162306a36Sopenharmony_ci}; 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_cistruct param { 8462306a36Sopenharmony_ci int is_present; 8562306a36Sopenharmony_ci union { 8662306a36Sopenharmony_ci void *ptr; 8762306a36Sopenharmony_ci u32 dword; 8862306a36Sopenharmony_ci u16 word; 8962306a36Sopenharmony_ci u8 byte; 9062306a36Sopenharmony_ci }; 9162306a36Sopenharmony_ci}; 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci/* HIP message type */ 9462306a36Sopenharmony_ci#define SIERRA_NET_HIP_EXTENDEDID 0x7F 9562306a36Sopenharmony_ci#define SIERRA_NET_HIP_HSYNC_ID 0x60 /* Modem -> host */ 9662306a36Sopenharmony_ci#define SIERRA_NET_HIP_RESTART_ID 0x62 /* Modem -> host */ 9762306a36Sopenharmony_ci#define SIERRA_NET_HIP_MSYNC_ID 0x20 /* Host -> modem */ 9862306a36Sopenharmony_ci#define SIERRA_NET_HIP_SHUTD_ID 0x26 /* Host -> modem */ 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci#define SIERRA_NET_HIP_EXT_IP_IN_ID 0x0202 10162306a36Sopenharmony_ci#define SIERRA_NET_HIP_EXT_IP_OUT_ID 0x0002 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci/* 3G UMTS Link Sense Indication definitions */ 10462306a36Sopenharmony_ci#define SIERRA_NET_HIP_LSI_UMTSID 0x78 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci/* Reverse Channel Grant Indication HIP message */ 10762306a36Sopenharmony_ci#define SIERRA_NET_HIP_RCGI 0x64 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci/* LSI Protocol types */ 11062306a36Sopenharmony_ci#define SIERRA_NET_PROTOCOL_UMTS 0x01 11162306a36Sopenharmony_ci#define SIERRA_NET_PROTOCOL_UMTS_DS 0x04 11262306a36Sopenharmony_ci/* LSI Coverage */ 11362306a36Sopenharmony_ci#define SIERRA_NET_COVERAGE_NONE 0x00 11462306a36Sopenharmony_ci#define SIERRA_NET_COVERAGE_NOPACKET 0x01 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci/* LSI Session */ 11762306a36Sopenharmony_ci#define SIERRA_NET_SESSION_IDLE 0x00 11862306a36Sopenharmony_ci/* LSI Link types */ 11962306a36Sopenharmony_ci#define SIERRA_NET_AS_LINK_TYPE_IPV4 0x00 12062306a36Sopenharmony_ci#define SIERRA_NET_AS_LINK_TYPE_IPV6 0x02 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_cistruct lsi_umts { 12362306a36Sopenharmony_ci u8 protocol; 12462306a36Sopenharmony_ci u8 unused1; 12562306a36Sopenharmony_ci __be16 length; 12662306a36Sopenharmony_ci /* eventually use a union for the rest - assume umts for now */ 12762306a36Sopenharmony_ci u8 coverage; 12862306a36Sopenharmony_ci u8 network_len; /* network name len */ 12962306a36Sopenharmony_ci u8 network[40]; /* network name (UCS2, bigendian) */ 13062306a36Sopenharmony_ci u8 session_state; 13162306a36Sopenharmony_ci u8 unused3[33]; 13262306a36Sopenharmony_ci} __packed; 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_cistruct lsi_umts_single { 13562306a36Sopenharmony_ci struct lsi_umts lsi; 13662306a36Sopenharmony_ci u8 link_type; 13762306a36Sopenharmony_ci u8 pdp_addr_len; /* NW-supplied PDP address len */ 13862306a36Sopenharmony_ci u8 pdp_addr[16]; /* NW-supplied PDP address (bigendian)) */ 13962306a36Sopenharmony_ci u8 unused4[23]; 14062306a36Sopenharmony_ci u8 dns1_addr_len; /* NW-supplied 1st DNS address len (bigendian) */ 14162306a36Sopenharmony_ci u8 dns1_addr[16]; /* NW-supplied 1st DNS address */ 14262306a36Sopenharmony_ci u8 dns2_addr_len; /* NW-supplied 2nd DNS address len */ 14362306a36Sopenharmony_ci u8 dns2_addr[16]; /* NW-supplied 2nd DNS address (bigendian)*/ 14462306a36Sopenharmony_ci u8 wins1_addr_len; /* NW-supplied 1st Wins address len */ 14562306a36Sopenharmony_ci u8 wins1_addr[16]; /* NW-supplied 1st Wins address (bigendian)*/ 14662306a36Sopenharmony_ci u8 wins2_addr_len; /* NW-supplied 2nd Wins address len */ 14762306a36Sopenharmony_ci u8 wins2_addr[16]; /* NW-supplied 2nd Wins address (bigendian) */ 14862306a36Sopenharmony_ci u8 unused5[4]; 14962306a36Sopenharmony_ci u8 gw_addr_len; /* NW-supplied GW address len */ 15062306a36Sopenharmony_ci u8 gw_addr[16]; /* NW-supplied GW address (bigendian) */ 15162306a36Sopenharmony_ci u8 reserved[8]; 15262306a36Sopenharmony_ci} __packed; 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_cistruct lsi_umts_dual { 15562306a36Sopenharmony_ci struct lsi_umts lsi; 15662306a36Sopenharmony_ci u8 pdp_addr4_len; /* NW-supplied PDP IPv4 address len */ 15762306a36Sopenharmony_ci u8 pdp_addr4[4]; /* NW-supplied PDP IPv4 address (bigendian)) */ 15862306a36Sopenharmony_ci u8 pdp_addr6_len; /* NW-supplied PDP IPv6 address len */ 15962306a36Sopenharmony_ci u8 pdp_addr6[16]; /* NW-supplied PDP IPv6 address (bigendian)) */ 16062306a36Sopenharmony_ci u8 unused4[23]; 16162306a36Sopenharmony_ci u8 dns1_addr4_len; /* NW-supplied 1st DNS v4 address len (bigendian) */ 16262306a36Sopenharmony_ci u8 dns1_addr4[4]; /* NW-supplied 1st DNS v4 address */ 16362306a36Sopenharmony_ci u8 dns1_addr6_len; /* NW-supplied 1st DNS v6 address len */ 16462306a36Sopenharmony_ci u8 dns1_addr6[16]; /* NW-supplied 1st DNS v6 address (bigendian)*/ 16562306a36Sopenharmony_ci u8 dns2_addr4_len; /* NW-supplied 2nd DNS v4 address len (bigendian) */ 16662306a36Sopenharmony_ci u8 dns2_addr4[4]; /* NW-supplied 2nd DNS v4 address */ 16762306a36Sopenharmony_ci u8 dns2_addr6_len; /* NW-supplied 2nd DNS v6 address len */ 16862306a36Sopenharmony_ci u8 dns2_addr6[16]; /* NW-supplied 2nd DNS v6 address (bigendian)*/ 16962306a36Sopenharmony_ci u8 unused5[68]; 17062306a36Sopenharmony_ci} __packed; 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci#define SIERRA_NET_LSI_COMMON_LEN 4 17362306a36Sopenharmony_ci#define SIERRA_NET_LSI_UMTS_LEN (sizeof(struct lsi_umts_single)) 17462306a36Sopenharmony_ci#define SIERRA_NET_LSI_UMTS_STATUS_LEN \ 17562306a36Sopenharmony_ci (SIERRA_NET_LSI_UMTS_LEN - SIERRA_NET_LSI_COMMON_LEN) 17662306a36Sopenharmony_ci#define SIERRA_NET_LSI_UMTS_DS_LEN (sizeof(struct lsi_umts_dual)) 17762306a36Sopenharmony_ci#define SIERRA_NET_LSI_UMTS_DS_STATUS_LEN \ 17862306a36Sopenharmony_ci (SIERRA_NET_LSI_UMTS_DS_LEN - SIERRA_NET_LSI_COMMON_LEN) 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci/* Our own net device operations structure */ 18162306a36Sopenharmony_cistatic const struct net_device_ops sierra_net_device_ops = { 18262306a36Sopenharmony_ci .ndo_open = usbnet_open, 18362306a36Sopenharmony_ci .ndo_stop = usbnet_stop, 18462306a36Sopenharmony_ci .ndo_start_xmit = usbnet_start_xmit, 18562306a36Sopenharmony_ci .ndo_tx_timeout = usbnet_tx_timeout, 18662306a36Sopenharmony_ci .ndo_change_mtu = usbnet_change_mtu, 18762306a36Sopenharmony_ci .ndo_get_stats64 = dev_get_tstats64, 18862306a36Sopenharmony_ci .ndo_set_mac_address = eth_mac_addr, 18962306a36Sopenharmony_ci .ndo_validate_addr = eth_validate_addr, 19062306a36Sopenharmony_ci}; 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ci/* get private data associated with passed in usbnet device */ 19362306a36Sopenharmony_cistatic inline struct sierra_net_data *sierra_net_get_private(struct usbnet *dev) 19462306a36Sopenharmony_ci{ 19562306a36Sopenharmony_ci return (struct sierra_net_data *)dev->data[0]; 19662306a36Sopenharmony_ci} 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci/* set private data associated with passed in usbnet device */ 19962306a36Sopenharmony_cistatic inline void sierra_net_set_private(struct usbnet *dev, 20062306a36Sopenharmony_ci struct sierra_net_data *priv) 20162306a36Sopenharmony_ci{ 20262306a36Sopenharmony_ci dev->data[0] = (unsigned long)priv; 20362306a36Sopenharmony_ci} 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ci/* is packet IPv4/IPv6 */ 20662306a36Sopenharmony_cistatic inline int is_ip(struct sk_buff *skb) 20762306a36Sopenharmony_ci{ 20862306a36Sopenharmony_ci return skb->protocol == cpu_to_be16(ETH_P_IP) || 20962306a36Sopenharmony_ci skb->protocol == cpu_to_be16(ETH_P_IPV6); 21062306a36Sopenharmony_ci} 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci/* 21362306a36Sopenharmony_ci * check passed in packet and make sure that: 21462306a36Sopenharmony_ci * - it is linear (no scatter/gather) 21562306a36Sopenharmony_ci * - it is ethernet (mac_header properly set) 21662306a36Sopenharmony_ci */ 21762306a36Sopenharmony_cistatic int check_ethip_packet(struct sk_buff *skb, struct usbnet *dev) 21862306a36Sopenharmony_ci{ 21962306a36Sopenharmony_ci skb_reset_mac_header(skb); /* ethernet header */ 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_ci if (skb_is_nonlinear(skb)) { 22262306a36Sopenharmony_ci netdev_err(dev->net, "Non linear buffer-dropping\n"); 22362306a36Sopenharmony_ci return 0; 22462306a36Sopenharmony_ci } 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci if (!pskb_may_pull(skb, ETH_HLEN)) 22762306a36Sopenharmony_ci return 0; 22862306a36Sopenharmony_ci skb->protocol = eth_hdr(skb)->h_proto; 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ci return 1; 23162306a36Sopenharmony_ci} 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_cistatic const u8 *save16bit(struct param *p, const u8 *datap) 23462306a36Sopenharmony_ci{ 23562306a36Sopenharmony_ci p->is_present = 1; 23662306a36Sopenharmony_ci p->word = get_unaligned_be16(datap); 23762306a36Sopenharmony_ci return datap + sizeof(p->word); 23862306a36Sopenharmony_ci} 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_cistatic const u8 *save8bit(struct param *p, const u8 *datap) 24162306a36Sopenharmony_ci{ 24262306a36Sopenharmony_ci p->is_present = 1; 24362306a36Sopenharmony_ci p->byte = *datap; 24462306a36Sopenharmony_ci return datap + sizeof(p->byte); 24562306a36Sopenharmony_ci} 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_ci/*----------------------------------------------------------------------------* 24862306a36Sopenharmony_ci * BEGIN HIP * 24962306a36Sopenharmony_ci *----------------------------------------------------------------------------*/ 25062306a36Sopenharmony_ci/* HIP header */ 25162306a36Sopenharmony_ci#define SIERRA_NET_HIP_HDR_LEN 4 25262306a36Sopenharmony_ci/* Extended HIP header */ 25362306a36Sopenharmony_ci#define SIERRA_NET_HIP_EXT_HDR_LEN 6 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_cistruct hip_hdr { 25662306a36Sopenharmony_ci int hdrlen; 25762306a36Sopenharmony_ci struct param payload_len; 25862306a36Sopenharmony_ci struct param msgid; 25962306a36Sopenharmony_ci struct param msgspecific; 26062306a36Sopenharmony_ci struct param extmsgid; 26162306a36Sopenharmony_ci}; 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_cistatic int parse_hip(const u8 *buf, const u32 buflen, struct hip_hdr *hh) 26462306a36Sopenharmony_ci{ 26562306a36Sopenharmony_ci const u8 *curp = buf; 26662306a36Sopenharmony_ci int padded; 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_ci if (buflen < SIERRA_NET_HIP_HDR_LEN) 26962306a36Sopenharmony_ci return -EPROTO; 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_ci curp = save16bit(&hh->payload_len, curp); 27262306a36Sopenharmony_ci curp = save8bit(&hh->msgid, curp); 27362306a36Sopenharmony_ci curp = save8bit(&hh->msgspecific, curp); 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_ci padded = hh->msgid.byte & 0x80; 27662306a36Sopenharmony_ci hh->msgid.byte &= 0x7F; /* 7 bits */ 27762306a36Sopenharmony_ci 27862306a36Sopenharmony_ci hh->extmsgid.is_present = (hh->msgid.byte == SIERRA_NET_HIP_EXTENDEDID); 27962306a36Sopenharmony_ci if (hh->extmsgid.is_present) { 28062306a36Sopenharmony_ci if (buflen < SIERRA_NET_HIP_EXT_HDR_LEN) 28162306a36Sopenharmony_ci return -EPROTO; 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_ci hh->payload_len.word &= 0x3FFF; /* 14 bits */ 28462306a36Sopenharmony_ci 28562306a36Sopenharmony_ci curp = save16bit(&hh->extmsgid, curp); 28662306a36Sopenharmony_ci hh->extmsgid.word &= 0x03FF; /* 10 bits */ 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_ci hh->hdrlen = SIERRA_NET_HIP_EXT_HDR_LEN; 28962306a36Sopenharmony_ci } else { 29062306a36Sopenharmony_ci hh->payload_len.word &= 0x07FF; /* 11 bits */ 29162306a36Sopenharmony_ci hh->hdrlen = SIERRA_NET_HIP_HDR_LEN; 29262306a36Sopenharmony_ci } 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_ci if (padded) { 29562306a36Sopenharmony_ci hh->hdrlen++; 29662306a36Sopenharmony_ci hh->payload_len.word--; 29762306a36Sopenharmony_ci } 29862306a36Sopenharmony_ci 29962306a36Sopenharmony_ci /* if real packet shorter than the claimed length */ 30062306a36Sopenharmony_ci if (buflen < (hh->hdrlen + hh->payload_len.word)) 30162306a36Sopenharmony_ci return -EINVAL; 30262306a36Sopenharmony_ci 30362306a36Sopenharmony_ci return 0; 30462306a36Sopenharmony_ci} 30562306a36Sopenharmony_ci 30662306a36Sopenharmony_cistatic void build_hip(u8 *buf, const u16 payloadlen, 30762306a36Sopenharmony_ci struct sierra_net_data *priv) 30862306a36Sopenharmony_ci{ 30962306a36Sopenharmony_ci /* the following doesn't have the full functionality. We 31062306a36Sopenharmony_ci * currently build only one kind of header, so it is faster this way 31162306a36Sopenharmony_ci */ 31262306a36Sopenharmony_ci put_unaligned_be16(payloadlen, buf); 31362306a36Sopenharmony_ci memcpy(buf+2, priv->tx_hdr_template, sizeof(priv->tx_hdr_template)); 31462306a36Sopenharmony_ci} 31562306a36Sopenharmony_ci/*----------------------------------------------------------------------------* 31662306a36Sopenharmony_ci * END HIP * 31762306a36Sopenharmony_ci *----------------------------------------------------------------------------*/ 31862306a36Sopenharmony_ci 31962306a36Sopenharmony_cistatic int sierra_net_send_cmd(struct usbnet *dev, 32062306a36Sopenharmony_ci u8 *cmd, int cmdlen, const char * cmd_name) 32162306a36Sopenharmony_ci{ 32262306a36Sopenharmony_ci struct sierra_net_data *priv = sierra_net_get_private(dev); 32362306a36Sopenharmony_ci int status; 32462306a36Sopenharmony_ci 32562306a36Sopenharmony_ci status = usbnet_write_cmd(dev, USB_CDC_SEND_ENCAPSULATED_COMMAND, 32662306a36Sopenharmony_ci USB_DIR_OUT|USB_TYPE_CLASS|USB_RECIP_INTERFACE, 32762306a36Sopenharmony_ci 0, priv->ifnum, cmd, cmdlen); 32862306a36Sopenharmony_ci 32962306a36Sopenharmony_ci if (status != cmdlen && status != -ENODEV) 33062306a36Sopenharmony_ci netdev_err(dev->net, "Submit %s failed %d\n", cmd_name, status); 33162306a36Sopenharmony_ci 33262306a36Sopenharmony_ci return status; 33362306a36Sopenharmony_ci} 33462306a36Sopenharmony_ci 33562306a36Sopenharmony_cistatic int sierra_net_send_sync(struct usbnet *dev) 33662306a36Sopenharmony_ci{ 33762306a36Sopenharmony_ci int status; 33862306a36Sopenharmony_ci struct sierra_net_data *priv = sierra_net_get_private(dev); 33962306a36Sopenharmony_ci 34062306a36Sopenharmony_ci dev_dbg(&dev->udev->dev, "%s", __func__); 34162306a36Sopenharmony_ci 34262306a36Sopenharmony_ci status = sierra_net_send_cmd(dev, priv->sync_msg, 34362306a36Sopenharmony_ci sizeof(priv->sync_msg), "SYNC"); 34462306a36Sopenharmony_ci 34562306a36Sopenharmony_ci return status; 34662306a36Sopenharmony_ci} 34762306a36Sopenharmony_ci 34862306a36Sopenharmony_cistatic void sierra_net_set_ctx_index(struct sierra_net_data *priv, u8 ctx_ix) 34962306a36Sopenharmony_ci{ 35062306a36Sopenharmony_ci dev_dbg(&(priv->usbnet->udev->dev), "%s %d", __func__, ctx_ix); 35162306a36Sopenharmony_ci priv->tx_hdr_template[0] = 0x3F; 35262306a36Sopenharmony_ci priv->tx_hdr_template[1] = ctx_ix; 35362306a36Sopenharmony_ci *((__be16 *)&priv->tx_hdr_template[2]) = 35462306a36Sopenharmony_ci cpu_to_be16(SIERRA_NET_HIP_EXT_IP_OUT_ID); 35562306a36Sopenharmony_ci} 35662306a36Sopenharmony_ci 35762306a36Sopenharmony_cistatic int sierra_net_parse_lsi(struct usbnet *dev, char *data, int datalen) 35862306a36Sopenharmony_ci{ 35962306a36Sopenharmony_ci struct lsi_umts *lsi = (struct lsi_umts *)data; 36062306a36Sopenharmony_ci u32 expected_length; 36162306a36Sopenharmony_ci 36262306a36Sopenharmony_ci if (datalen < sizeof(struct lsi_umts_single)) { 36362306a36Sopenharmony_ci netdev_err(dev->net, "%s: Data length %d, exp >= %zu\n", 36462306a36Sopenharmony_ci __func__, datalen, sizeof(struct lsi_umts_single)); 36562306a36Sopenharmony_ci return -1; 36662306a36Sopenharmony_ci } 36762306a36Sopenharmony_ci 36862306a36Sopenharmony_ci /* Validate the session state */ 36962306a36Sopenharmony_ci if (lsi->session_state == SIERRA_NET_SESSION_IDLE) { 37062306a36Sopenharmony_ci netdev_err(dev->net, "Session idle, 0x%02x\n", 37162306a36Sopenharmony_ci lsi->session_state); 37262306a36Sopenharmony_ci return 0; 37362306a36Sopenharmony_ci } 37462306a36Sopenharmony_ci 37562306a36Sopenharmony_ci /* Validate the protocol - only support UMTS for now */ 37662306a36Sopenharmony_ci if (lsi->protocol == SIERRA_NET_PROTOCOL_UMTS) { 37762306a36Sopenharmony_ci struct lsi_umts_single *single = (struct lsi_umts_single *)lsi; 37862306a36Sopenharmony_ci 37962306a36Sopenharmony_ci /* Validate the link type */ 38062306a36Sopenharmony_ci if (single->link_type != SIERRA_NET_AS_LINK_TYPE_IPV4 && 38162306a36Sopenharmony_ci single->link_type != SIERRA_NET_AS_LINK_TYPE_IPV6) { 38262306a36Sopenharmony_ci netdev_err(dev->net, "Link type unsupported: 0x%02x\n", 38362306a36Sopenharmony_ci single->link_type); 38462306a36Sopenharmony_ci return -1; 38562306a36Sopenharmony_ci } 38662306a36Sopenharmony_ci expected_length = SIERRA_NET_LSI_UMTS_STATUS_LEN; 38762306a36Sopenharmony_ci } else if (lsi->protocol == SIERRA_NET_PROTOCOL_UMTS_DS) { 38862306a36Sopenharmony_ci expected_length = SIERRA_NET_LSI_UMTS_DS_STATUS_LEN; 38962306a36Sopenharmony_ci } else { 39062306a36Sopenharmony_ci netdev_err(dev->net, "Protocol unsupported, 0x%02x\n", 39162306a36Sopenharmony_ci lsi->protocol); 39262306a36Sopenharmony_ci return -1; 39362306a36Sopenharmony_ci } 39462306a36Sopenharmony_ci 39562306a36Sopenharmony_ci if (be16_to_cpu(lsi->length) != expected_length) { 39662306a36Sopenharmony_ci netdev_err(dev->net, "%s: LSI_UMTS_STATUS_LEN %d, exp %u\n", 39762306a36Sopenharmony_ci __func__, be16_to_cpu(lsi->length), expected_length); 39862306a36Sopenharmony_ci return -1; 39962306a36Sopenharmony_ci } 40062306a36Sopenharmony_ci 40162306a36Sopenharmony_ci /* Validate the coverage */ 40262306a36Sopenharmony_ci if (lsi->coverage == SIERRA_NET_COVERAGE_NONE || 40362306a36Sopenharmony_ci lsi->coverage == SIERRA_NET_COVERAGE_NOPACKET) { 40462306a36Sopenharmony_ci netdev_err(dev->net, "No coverage, 0x%02x\n", lsi->coverage); 40562306a36Sopenharmony_ci return 0; 40662306a36Sopenharmony_ci } 40762306a36Sopenharmony_ci 40862306a36Sopenharmony_ci /* Set link_sense true */ 40962306a36Sopenharmony_ci return 1; 41062306a36Sopenharmony_ci} 41162306a36Sopenharmony_ci 41262306a36Sopenharmony_cistatic void sierra_net_handle_lsi(struct usbnet *dev, char *data, 41362306a36Sopenharmony_ci struct hip_hdr *hh) 41462306a36Sopenharmony_ci{ 41562306a36Sopenharmony_ci struct sierra_net_data *priv = sierra_net_get_private(dev); 41662306a36Sopenharmony_ci int link_up; 41762306a36Sopenharmony_ci 41862306a36Sopenharmony_ci link_up = sierra_net_parse_lsi(dev, data + hh->hdrlen, 41962306a36Sopenharmony_ci hh->payload_len.word); 42062306a36Sopenharmony_ci if (link_up < 0) { 42162306a36Sopenharmony_ci netdev_err(dev->net, "Invalid LSI\n"); 42262306a36Sopenharmony_ci return; 42362306a36Sopenharmony_ci } 42462306a36Sopenharmony_ci if (link_up) { 42562306a36Sopenharmony_ci sierra_net_set_ctx_index(priv, hh->msgspecific.byte); 42662306a36Sopenharmony_ci priv->link_up = 1; 42762306a36Sopenharmony_ci } else { 42862306a36Sopenharmony_ci priv->link_up = 0; 42962306a36Sopenharmony_ci } 43062306a36Sopenharmony_ci usbnet_link_change(dev, link_up, 0); 43162306a36Sopenharmony_ci} 43262306a36Sopenharmony_ci 43362306a36Sopenharmony_cistatic void sierra_net_dosync(struct usbnet *dev) 43462306a36Sopenharmony_ci{ 43562306a36Sopenharmony_ci int status; 43662306a36Sopenharmony_ci struct sierra_net_data *priv = sierra_net_get_private(dev); 43762306a36Sopenharmony_ci 43862306a36Sopenharmony_ci dev_dbg(&dev->udev->dev, "%s", __func__); 43962306a36Sopenharmony_ci 44062306a36Sopenharmony_ci /* The SIERRA_NET_HIP_MSYNC_ID command appears to request that the 44162306a36Sopenharmony_ci * firmware restart itself. After restarting, the modem will respond 44262306a36Sopenharmony_ci * with the SIERRA_NET_HIP_RESTART_ID indication. The driver continues 44362306a36Sopenharmony_ci * sending MSYNC commands every few seconds until it receives the 44462306a36Sopenharmony_ci * RESTART event from the firmware 44562306a36Sopenharmony_ci */ 44662306a36Sopenharmony_ci 44762306a36Sopenharmony_ci /* tell modem we are ready */ 44862306a36Sopenharmony_ci status = sierra_net_send_sync(dev); 44962306a36Sopenharmony_ci if (status < 0) 45062306a36Sopenharmony_ci netdev_err(dev->net, 45162306a36Sopenharmony_ci "Send SYNC failed, status %d\n", status); 45262306a36Sopenharmony_ci status = sierra_net_send_sync(dev); 45362306a36Sopenharmony_ci if (status < 0) 45462306a36Sopenharmony_ci netdev_err(dev->net, 45562306a36Sopenharmony_ci "Send SYNC failed, status %d\n", status); 45662306a36Sopenharmony_ci 45762306a36Sopenharmony_ci /* Now, start a timer and make sure we get the Restart Indication */ 45862306a36Sopenharmony_ci priv->sync_timer.expires = jiffies + SIERRA_NET_SYNCDELAY; 45962306a36Sopenharmony_ci add_timer(&priv->sync_timer); 46062306a36Sopenharmony_ci} 46162306a36Sopenharmony_ci 46262306a36Sopenharmony_cistatic void sierra_net_kevent(struct work_struct *work) 46362306a36Sopenharmony_ci{ 46462306a36Sopenharmony_ci struct sierra_net_data *priv = 46562306a36Sopenharmony_ci container_of(work, struct sierra_net_data, sierra_net_kevent); 46662306a36Sopenharmony_ci struct usbnet *dev = priv->usbnet; 46762306a36Sopenharmony_ci int len; 46862306a36Sopenharmony_ci int err; 46962306a36Sopenharmony_ci u8 *buf; 47062306a36Sopenharmony_ci u8 ifnum; 47162306a36Sopenharmony_ci 47262306a36Sopenharmony_ci if (test_bit(SIERRA_NET_EVENT_RESP_AVAIL, &priv->kevent_flags)) { 47362306a36Sopenharmony_ci clear_bit(SIERRA_NET_EVENT_RESP_AVAIL, &priv->kevent_flags); 47462306a36Sopenharmony_ci 47562306a36Sopenharmony_ci /* Query the modem for the LSI message */ 47662306a36Sopenharmony_ci buf = kzalloc(SIERRA_NET_USBCTL_BUF_LEN, GFP_KERNEL); 47762306a36Sopenharmony_ci if (!buf) 47862306a36Sopenharmony_ci return; 47962306a36Sopenharmony_ci 48062306a36Sopenharmony_ci ifnum = priv->ifnum; 48162306a36Sopenharmony_ci len = usb_control_msg(dev->udev, usb_rcvctrlpipe(dev->udev, 0), 48262306a36Sopenharmony_ci USB_CDC_GET_ENCAPSULATED_RESPONSE, 48362306a36Sopenharmony_ci USB_DIR_IN|USB_TYPE_CLASS|USB_RECIP_INTERFACE, 48462306a36Sopenharmony_ci 0, ifnum, buf, SIERRA_NET_USBCTL_BUF_LEN, 48562306a36Sopenharmony_ci USB_CTRL_SET_TIMEOUT); 48662306a36Sopenharmony_ci 48762306a36Sopenharmony_ci if (len < 0) { 48862306a36Sopenharmony_ci netdev_err(dev->net, 48962306a36Sopenharmony_ci "usb_control_msg failed, status %d\n", len); 49062306a36Sopenharmony_ci } else { 49162306a36Sopenharmony_ci struct hip_hdr hh; 49262306a36Sopenharmony_ci 49362306a36Sopenharmony_ci dev_dbg(&dev->udev->dev, "%s: Received status message," 49462306a36Sopenharmony_ci " %04x bytes", __func__, len); 49562306a36Sopenharmony_ci 49662306a36Sopenharmony_ci err = parse_hip(buf, len, &hh); 49762306a36Sopenharmony_ci if (err) { 49862306a36Sopenharmony_ci netdev_err(dev->net, "%s: Bad packet," 49962306a36Sopenharmony_ci " parse result %d\n", __func__, err); 50062306a36Sopenharmony_ci kfree(buf); 50162306a36Sopenharmony_ci return; 50262306a36Sopenharmony_ci } 50362306a36Sopenharmony_ci 50462306a36Sopenharmony_ci /* Validate packet length */ 50562306a36Sopenharmony_ci if (len != hh.hdrlen + hh.payload_len.word) { 50662306a36Sopenharmony_ci netdev_err(dev->net, "%s: Bad packet, received" 50762306a36Sopenharmony_ci " %d, expected %d\n", __func__, len, 50862306a36Sopenharmony_ci hh.hdrlen + hh.payload_len.word); 50962306a36Sopenharmony_ci kfree(buf); 51062306a36Sopenharmony_ci return; 51162306a36Sopenharmony_ci } 51262306a36Sopenharmony_ci 51362306a36Sopenharmony_ci /* Switch on received message types */ 51462306a36Sopenharmony_ci switch (hh.msgid.byte) { 51562306a36Sopenharmony_ci case SIERRA_NET_HIP_LSI_UMTSID: 51662306a36Sopenharmony_ci dev_dbg(&dev->udev->dev, "LSI for ctx:%d", 51762306a36Sopenharmony_ci hh.msgspecific.byte); 51862306a36Sopenharmony_ci sierra_net_handle_lsi(dev, buf, &hh); 51962306a36Sopenharmony_ci break; 52062306a36Sopenharmony_ci case SIERRA_NET_HIP_RESTART_ID: 52162306a36Sopenharmony_ci dev_dbg(&dev->udev->dev, "Restart reported: %d," 52262306a36Sopenharmony_ci " stopping sync timer", 52362306a36Sopenharmony_ci hh.msgspecific.byte); 52462306a36Sopenharmony_ci /* Got sync resp - stop timer & clear mask */ 52562306a36Sopenharmony_ci del_timer_sync(&priv->sync_timer); 52662306a36Sopenharmony_ci clear_bit(SIERRA_NET_TIMER_EXPIRY, 52762306a36Sopenharmony_ci &priv->kevent_flags); 52862306a36Sopenharmony_ci break; 52962306a36Sopenharmony_ci case SIERRA_NET_HIP_HSYNC_ID: 53062306a36Sopenharmony_ci dev_dbg(&dev->udev->dev, "SYNC received"); 53162306a36Sopenharmony_ci err = sierra_net_send_sync(dev); 53262306a36Sopenharmony_ci if (err < 0) 53362306a36Sopenharmony_ci netdev_err(dev->net, 53462306a36Sopenharmony_ci "Send SYNC failed %d\n", err); 53562306a36Sopenharmony_ci break; 53662306a36Sopenharmony_ci case SIERRA_NET_HIP_EXTENDEDID: 53762306a36Sopenharmony_ci netdev_err(dev->net, "Unrecognized HIP msg, " 53862306a36Sopenharmony_ci "extmsgid 0x%04x\n", hh.extmsgid.word); 53962306a36Sopenharmony_ci break; 54062306a36Sopenharmony_ci case SIERRA_NET_HIP_RCGI: 54162306a36Sopenharmony_ci /* Ignored */ 54262306a36Sopenharmony_ci break; 54362306a36Sopenharmony_ci default: 54462306a36Sopenharmony_ci netdev_err(dev->net, "Unrecognized HIP msg, " 54562306a36Sopenharmony_ci "msgid 0x%02x\n", hh.msgid.byte); 54662306a36Sopenharmony_ci break; 54762306a36Sopenharmony_ci } 54862306a36Sopenharmony_ci } 54962306a36Sopenharmony_ci kfree(buf); 55062306a36Sopenharmony_ci } 55162306a36Sopenharmony_ci /* The sync timer bit might be set */ 55262306a36Sopenharmony_ci if (test_bit(SIERRA_NET_TIMER_EXPIRY, &priv->kevent_flags)) { 55362306a36Sopenharmony_ci clear_bit(SIERRA_NET_TIMER_EXPIRY, &priv->kevent_flags); 55462306a36Sopenharmony_ci dev_dbg(&dev->udev->dev, "Deferred sync timer expiry"); 55562306a36Sopenharmony_ci sierra_net_dosync(priv->usbnet); 55662306a36Sopenharmony_ci } 55762306a36Sopenharmony_ci 55862306a36Sopenharmony_ci if (priv->kevent_flags) 55962306a36Sopenharmony_ci dev_dbg(&dev->udev->dev, "sierra_net_kevent done, " 56062306a36Sopenharmony_ci "kevent_flags = 0x%lx", priv->kevent_flags); 56162306a36Sopenharmony_ci} 56262306a36Sopenharmony_ci 56362306a36Sopenharmony_cistatic void sierra_net_defer_kevent(struct usbnet *dev, int work) 56462306a36Sopenharmony_ci{ 56562306a36Sopenharmony_ci struct sierra_net_data *priv = sierra_net_get_private(dev); 56662306a36Sopenharmony_ci 56762306a36Sopenharmony_ci set_bit(work, &priv->kevent_flags); 56862306a36Sopenharmony_ci schedule_work(&priv->sierra_net_kevent); 56962306a36Sopenharmony_ci} 57062306a36Sopenharmony_ci 57162306a36Sopenharmony_ci/* 57262306a36Sopenharmony_ci * Sync Retransmit Timer Handler. On expiry, kick the work queue 57362306a36Sopenharmony_ci */ 57462306a36Sopenharmony_cistatic void sierra_sync_timer(struct timer_list *t) 57562306a36Sopenharmony_ci{ 57662306a36Sopenharmony_ci struct sierra_net_data *priv = from_timer(priv, t, sync_timer); 57762306a36Sopenharmony_ci struct usbnet *dev = priv->usbnet; 57862306a36Sopenharmony_ci 57962306a36Sopenharmony_ci dev_dbg(&dev->udev->dev, "%s", __func__); 58062306a36Sopenharmony_ci /* Kick the tasklet */ 58162306a36Sopenharmony_ci sierra_net_defer_kevent(dev, SIERRA_NET_TIMER_EXPIRY); 58262306a36Sopenharmony_ci} 58362306a36Sopenharmony_ci 58462306a36Sopenharmony_cistatic void sierra_net_status(struct usbnet *dev, struct urb *urb) 58562306a36Sopenharmony_ci{ 58662306a36Sopenharmony_ci struct usb_cdc_notification *event; 58762306a36Sopenharmony_ci 58862306a36Sopenharmony_ci dev_dbg(&dev->udev->dev, "%s", __func__); 58962306a36Sopenharmony_ci 59062306a36Sopenharmony_ci if (urb->actual_length < sizeof *event) 59162306a36Sopenharmony_ci return; 59262306a36Sopenharmony_ci 59362306a36Sopenharmony_ci /* Add cases to handle other standard notifications. */ 59462306a36Sopenharmony_ci event = urb->transfer_buffer; 59562306a36Sopenharmony_ci switch (event->bNotificationType) { 59662306a36Sopenharmony_ci case USB_CDC_NOTIFY_NETWORK_CONNECTION: 59762306a36Sopenharmony_ci case USB_CDC_NOTIFY_SPEED_CHANGE: 59862306a36Sopenharmony_ci /* USB 305 sends those */ 59962306a36Sopenharmony_ci break; 60062306a36Sopenharmony_ci case USB_CDC_NOTIFY_RESPONSE_AVAILABLE: 60162306a36Sopenharmony_ci sierra_net_defer_kevent(dev, SIERRA_NET_EVENT_RESP_AVAIL); 60262306a36Sopenharmony_ci break; 60362306a36Sopenharmony_ci default: 60462306a36Sopenharmony_ci netdev_err(dev->net, ": unexpected notification %02x!\n", 60562306a36Sopenharmony_ci event->bNotificationType); 60662306a36Sopenharmony_ci break; 60762306a36Sopenharmony_ci } 60862306a36Sopenharmony_ci} 60962306a36Sopenharmony_ci 61062306a36Sopenharmony_cistatic void sierra_net_get_drvinfo(struct net_device *net, 61162306a36Sopenharmony_ci struct ethtool_drvinfo *info) 61262306a36Sopenharmony_ci{ 61362306a36Sopenharmony_ci /* Inherit standard device info */ 61462306a36Sopenharmony_ci usbnet_get_drvinfo(net, info); 61562306a36Sopenharmony_ci strscpy(info->driver, driver_name, sizeof(info->driver)); 61662306a36Sopenharmony_ci strscpy(info->version, DRIVER_VERSION, sizeof(info->version)); 61762306a36Sopenharmony_ci} 61862306a36Sopenharmony_ci 61962306a36Sopenharmony_cistatic u32 sierra_net_get_link(struct net_device *net) 62062306a36Sopenharmony_ci{ 62162306a36Sopenharmony_ci struct usbnet *dev = netdev_priv(net); 62262306a36Sopenharmony_ci /* Report link is down whenever the interface is down */ 62362306a36Sopenharmony_ci return sierra_net_get_private(dev)->link_up && netif_running(net); 62462306a36Sopenharmony_ci} 62562306a36Sopenharmony_ci 62662306a36Sopenharmony_cistatic const struct ethtool_ops sierra_net_ethtool_ops = { 62762306a36Sopenharmony_ci .get_drvinfo = sierra_net_get_drvinfo, 62862306a36Sopenharmony_ci .get_link = sierra_net_get_link, 62962306a36Sopenharmony_ci .get_msglevel = usbnet_get_msglevel, 63062306a36Sopenharmony_ci .set_msglevel = usbnet_set_msglevel, 63162306a36Sopenharmony_ci .nway_reset = usbnet_nway_reset, 63262306a36Sopenharmony_ci .get_link_ksettings = usbnet_get_link_ksettings_mii, 63362306a36Sopenharmony_ci .set_link_ksettings = usbnet_set_link_ksettings_mii, 63462306a36Sopenharmony_ci}; 63562306a36Sopenharmony_ci 63662306a36Sopenharmony_cistatic int sierra_net_get_fw_attr(struct usbnet *dev, u16 *datap) 63762306a36Sopenharmony_ci{ 63862306a36Sopenharmony_ci int result = 0; 63962306a36Sopenharmony_ci __le16 attrdata; 64062306a36Sopenharmony_ci 64162306a36Sopenharmony_ci result = usbnet_read_cmd(dev, 64262306a36Sopenharmony_ci /* _u8 vendor specific request */ 64362306a36Sopenharmony_ci SWI_USB_REQUEST_GET_FW_ATTR, 64462306a36Sopenharmony_ci USB_DIR_IN | USB_TYPE_VENDOR, /* __u8 request type */ 64562306a36Sopenharmony_ci 0x0000, /* __u16 value not used */ 64662306a36Sopenharmony_ci 0x0000, /* __u16 index not used */ 64762306a36Sopenharmony_ci &attrdata, /* char *data */ 64862306a36Sopenharmony_ci sizeof(attrdata) /* __u16 size */ 64962306a36Sopenharmony_ci ); 65062306a36Sopenharmony_ci 65162306a36Sopenharmony_ci if (result < 0) 65262306a36Sopenharmony_ci return -EIO; 65362306a36Sopenharmony_ci 65462306a36Sopenharmony_ci *datap = le16_to_cpu(attrdata); 65562306a36Sopenharmony_ci return result; 65662306a36Sopenharmony_ci} 65762306a36Sopenharmony_ci 65862306a36Sopenharmony_ci/* 65962306a36Sopenharmony_ci * collects the bulk endpoints, the status endpoint. 66062306a36Sopenharmony_ci */ 66162306a36Sopenharmony_cistatic int sierra_net_bind(struct usbnet *dev, struct usb_interface *intf) 66262306a36Sopenharmony_ci{ 66362306a36Sopenharmony_ci u8 ifacenum; 66462306a36Sopenharmony_ci u8 numendpoints; 66562306a36Sopenharmony_ci u16 fwattr = 0; 66662306a36Sopenharmony_ci int status; 66762306a36Sopenharmony_ci struct sierra_net_data *priv; 66862306a36Sopenharmony_ci static const u8 sync_tmplate[sizeof(priv->sync_msg)] = { 66962306a36Sopenharmony_ci 0x00, 0x00, SIERRA_NET_HIP_MSYNC_ID, 0x00}; 67062306a36Sopenharmony_ci static const u8 shdwn_tmplate[sizeof(priv->shdwn_msg)] = { 67162306a36Sopenharmony_ci 0x00, 0x00, SIERRA_NET_HIP_SHUTD_ID, 0x00}; 67262306a36Sopenharmony_ci u8 mod[2]; 67362306a36Sopenharmony_ci 67462306a36Sopenharmony_ci dev_dbg(&dev->udev->dev, "%s", __func__); 67562306a36Sopenharmony_ci 67662306a36Sopenharmony_ci ifacenum = intf->cur_altsetting->desc.bInterfaceNumber; 67762306a36Sopenharmony_ci numendpoints = intf->cur_altsetting->desc.bNumEndpoints; 67862306a36Sopenharmony_ci /* We have three endpoints, bulk in and out, and a status */ 67962306a36Sopenharmony_ci if (numendpoints != 3) { 68062306a36Sopenharmony_ci dev_err(&dev->udev->dev, "Expected 3 endpoints, found: %d", 68162306a36Sopenharmony_ci numendpoints); 68262306a36Sopenharmony_ci return -ENODEV; 68362306a36Sopenharmony_ci } 68462306a36Sopenharmony_ci /* Status endpoint set in usbnet_get_endpoints() */ 68562306a36Sopenharmony_ci dev->status = NULL; 68662306a36Sopenharmony_ci status = usbnet_get_endpoints(dev, intf); 68762306a36Sopenharmony_ci if (status < 0) { 68862306a36Sopenharmony_ci dev_err(&dev->udev->dev, "Error in usbnet_get_endpoints (%d)", 68962306a36Sopenharmony_ci status); 69062306a36Sopenharmony_ci return -ENODEV; 69162306a36Sopenharmony_ci } 69262306a36Sopenharmony_ci /* Initialize sierra private data */ 69362306a36Sopenharmony_ci priv = kzalloc(sizeof *priv, GFP_KERNEL); 69462306a36Sopenharmony_ci if (!priv) 69562306a36Sopenharmony_ci return -ENOMEM; 69662306a36Sopenharmony_ci 69762306a36Sopenharmony_ci priv->usbnet = dev; 69862306a36Sopenharmony_ci priv->ifnum = ifacenum; 69962306a36Sopenharmony_ci dev->net->netdev_ops = &sierra_net_device_ops; 70062306a36Sopenharmony_ci 70162306a36Sopenharmony_ci /* change MAC addr to include, ifacenum, and to be unique */ 70262306a36Sopenharmony_ci mod[0] = atomic_inc_return(&iface_counter); 70362306a36Sopenharmony_ci mod[1] = ifacenum; 70462306a36Sopenharmony_ci dev_addr_mod(dev->net, ETH_ALEN - 2, mod, 2); 70562306a36Sopenharmony_ci 70662306a36Sopenharmony_ci /* prepare shutdown message template */ 70762306a36Sopenharmony_ci memcpy(priv->shdwn_msg, shdwn_tmplate, sizeof(priv->shdwn_msg)); 70862306a36Sopenharmony_ci /* set context index initially to 0 - prepares tx hdr template */ 70962306a36Sopenharmony_ci sierra_net_set_ctx_index(priv, 0); 71062306a36Sopenharmony_ci 71162306a36Sopenharmony_ci /* prepare sync message template */ 71262306a36Sopenharmony_ci memcpy(priv->sync_msg, sync_tmplate, sizeof(priv->sync_msg)); 71362306a36Sopenharmony_ci 71462306a36Sopenharmony_ci /* decrease the rx_urb_size and max_tx_size to 4k on USB 1.1 */ 71562306a36Sopenharmony_ci dev->rx_urb_size = SIERRA_NET_RX_URB_SIZE; 71662306a36Sopenharmony_ci if (dev->udev->speed != USB_SPEED_HIGH) 71762306a36Sopenharmony_ci dev->rx_urb_size = min_t(size_t, 4096, SIERRA_NET_RX_URB_SIZE); 71862306a36Sopenharmony_ci 71962306a36Sopenharmony_ci dev->net->hard_header_len += SIERRA_NET_HIP_EXT_HDR_LEN; 72062306a36Sopenharmony_ci dev->hard_mtu = dev->net->mtu + dev->net->hard_header_len; 72162306a36Sopenharmony_ci dev->net->max_mtu = SIERRA_NET_MAX_SUPPORTED_MTU; 72262306a36Sopenharmony_ci 72362306a36Sopenharmony_ci /* Set up the netdev */ 72462306a36Sopenharmony_ci dev->net->flags |= IFF_NOARP; 72562306a36Sopenharmony_ci dev->net->ethtool_ops = &sierra_net_ethtool_ops; 72662306a36Sopenharmony_ci netif_carrier_off(dev->net); 72762306a36Sopenharmony_ci 72862306a36Sopenharmony_ci sierra_net_set_private(dev, priv); 72962306a36Sopenharmony_ci 73062306a36Sopenharmony_ci priv->kevent_flags = 0; 73162306a36Sopenharmony_ci 73262306a36Sopenharmony_ci /* Use the shared workqueue */ 73362306a36Sopenharmony_ci INIT_WORK(&priv->sierra_net_kevent, sierra_net_kevent); 73462306a36Sopenharmony_ci 73562306a36Sopenharmony_ci /* Only need to do this once */ 73662306a36Sopenharmony_ci timer_setup(&priv->sync_timer, sierra_sync_timer, 0); 73762306a36Sopenharmony_ci 73862306a36Sopenharmony_ci /* verify fw attributes */ 73962306a36Sopenharmony_ci status = sierra_net_get_fw_attr(dev, &fwattr); 74062306a36Sopenharmony_ci dev_dbg(&dev->udev->dev, "Fw attr: %x\n", fwattr); 74162306a36Sopenharmony_ci 74262306a36Sopenharmony_ci /* test whether firmware supports DHCP */ 74362306a36Sopenharmony_ci if (!(status == sizeof(fwattr) && (fwattr & SWI_GET_FW_ATTR_MASK))) { 74462306a36Sopenharmony_ci /* found incompatible firmware version */ 74562306a36Sopenharmony_ci dev_err(&dev->udev->dev, "Incompatible driver and firmware" 74662306a36Sopenharmony_ci " versions\n"); 74762306a36Sopenharmony_ci kfree(priv); 74862306a36Sopenharmony_ci return -ENODEV; 74962306a36Sopenharmony_ci } 75062306a36Sopenharmony_ci 75162306a36Sopenharmony_ci return 0; 75262306a36Sopenharmony_ci} 75362306a36Sopenharmony_ci 75462306a36Sopenharmony_cistatic void sierra_net_unbind(struct usbnet *dev, struct usb_interface *intf) 75562306a36Sopenharmony_ci{ 75662306a36Sopenharmony_ci int status; 75762306a36Sopenharmony_ci struct sierra_net_data *priv = sierra_net_get_private(dev); 75862306a36Sopenharmony_ci 75962306a36Sopenharmony_ci dev_dbg(&dev->udev->dev, "%s", __func__); 76062306a36Sopenharmony_ci 76162306a36Sopenharmony_ci /* kill the timer and work */ 76262306a36Sopenharmony_ci timer_shutdown_sync(&priv->sync_timer); 76362306a36Sopenharmony_ci cancel_work_sync(&priv->sierra_net_kevent); 76462306a36Sopenharmony_ci 76562306a36Sopenharmony_ci /* tell modem we are going away */ 76662306a36Sopenharmony_ci status = sierra_net_send_cmd(dev, priv->shdwn_msg, 76762306a36Sopenharmony_ci sizeof(priv->shdwn_msg), "Shutdown"); 76862306a36Sopenharmony_ci if (status < 0) 76962306a36Sopenharmony_ci netdev_err(dev->net, 77062306a36Sopenharmony_ci "usb_control_msg failed, status %d\n", status); 77162306a36Sopenharmony_ci 77262306a36Sopenharmony_ci usbnet_status_stop(dev); 77362306a36Sopenharmony_ci 77462306a36Sopenharmony_ci sierra_net_set_private(dev, NULL); 77562306a36Sopenharmony_ci kfree(priv); 77662306a36Sopenharmony_ci} 77762306a36Sopenharmony_ci 77862306a36Sopenharmony_cistatic struct sk_buff *sierra_net_skb_clone(struct usbnet *dev, 77962306a36Sopenharmony_ci struct sk_buff *skb, int len) 78062306a36Sopenharmony_ci{ 78162306a36Sopenharmony_ci struct sk_buff *new_skb; 78262306a36Sopenharmony_ci 78362306a36Sopenharmony_ci /* clone skb */ 78462306a36Sopenharmony_ci new_skb = skb_clone(skb, GFP_ATOMIC); 78562306a36Sopenharmony_ci 78662306a36Sopenharmony_ci /* remove len bytes from original */ 78762306a36Sopenharmony_ci skb_pull(skb, len); 78862306a36Sopenharmony_ci 78962306a36Sopenharmony_ci /* trim next packet to it's length */ 79062306a36Sopenharmony_ci if (new_skb) { 79162306a36Sopenharmony_ci skb_trim(new_skb, len); 79262306a36Sopenharmony_ci } else { 79362306a36Sopenharmony_ci if (netif_msg_rx_err(dev)) 79462306a36Sopenharmony_ci netdev_err(dev->net, "failed to get skb\n"); 79562306a36Sopenharmony_ci dev->net->stats.rx_dropped++; 79662306a36Sopenharmony_ci } 79762306a36Sopenharmony_ci 79862306a36Sopenharmony_ci return new_skb; 79962306a36Sopenharmony_ci} 80062306a36Sopenharmony_ci 80162306a36Sopenharmony_ci/* ---------------------------- Receive data path ----------------------*/ 80262306a36Sopenharmony_cistatic int sierra_net_rx_fixup(struct usbnet *dev, struct sk_buff *skb) 80362306a36Sopenharmony_ci{ 80462306a36Sopenharmony_ci int err; 80562306a36Sopenharmony_ci struct hip_hdr hh; 80662306a36Sopenharmony_ci struct sk_buff *new_skb; 80762306a36Sopenharmony_ci 80862306a36Sopenharmony_ci dev_dbg(&dev->udev->dev, "%s", __func__); 80962306a36Sopenharmony_ci 81062306a36Sopenharmony_ci /* could contain multiple packets */ 81162306a36Sopenharmony_ci while (likely(skb->len)) { 81262306a36Sopenharmony_ci err = parse_hip(skb->data, skb->len, &hh); 81362306a36Sopenharmony_ci if (err) { 81462306a36Sopenharmony_ci if (netif_msg_rx_err(dev)) 81562306a36Sopenharmony_ci netdev_err(dev->net, "Invalid HIP header %d\n", 81662306a36Sopenharmony_ci err); 81762306a36Sopenharmony_ci /* dev->net->stats.rx_errors incremented by caller */ 81862306a36Sopenharmony_ci dev->net->stats.rx_length_errors++; 81962306a36Sopenharmony_ci return 0; 82062306a36Sopenharmony_ci } 82162306a36Sopenharmony_ci 82262306a36Sopenharmony_ci /* Validate Extended HIP header */ 82362306a36Sopenharmony_ci if (!hh.extmsgid.is_present 82462306a36Sopenharmony_ci || hh.extmsgid.word != SIERRA_NET_HIP_EXT_IP_IN_ID) { 82562306a36Sopenharmony_ci if (netif_msg_rx_err(dev)) 82662306a36Sopenharmony_ci netdev_err(dev->net, "HIP/ETH: Invalid pkt\n"); 82762306a36Sopenharmony_ci 82862306a36Sopenharmony_ci dev->net->stats.rx_frame_errors++; 82962306a36Sopenharmony_ci /* dev->net->stats.rx_errors incremented by caller */ 83062306a36Sopenharmony_ci return 0; 83162306a36Sopenharmony_ci } 83262306a36Sopenharmony_ci 83362306a36Sopenharmony_ci skb_pull(skb, hh.hdrlen); 83462306a36Sopenharmony_ci 83562306a36Sopenharmony_ci /* We are going to accept this packet, prepare it. 83662306a36Sopenharmony_ci * In case protocol is IPv6, keep it, otherwise force IPv4. 83762306a36Sopenharmony_ci */ 83862306a36Sopenharmony_ci skb_reset_mac_header(skb); 83962306a36Sopenharmony_ci if (eth_hdr(skb)->h_proto != cpu_to_be16(ETH_P_IPV6)) 84062306a36Sopenharmony_ci eth_hdr(skb)->h_proto = cpu_to_be16(ETH_P_IP); 84162306a36Sopenharmony_ci eth_zero_addr(eth_hdr(skb)->h_source); 84262306a36Sopenharmony_ci memcpy(eth_hdr(skb)->h_dest, dev->net->dev_addr, ETH_ALEN); 84362306a36Sopenharmony_ci 84462306a36Sopenharmony_ci /* Last packet in batch handled by usbnet */ 84562306a36Sopenharmony_ci if (hh.payload_len.word == skb->len) 84662306a36Sopenharmony_ci return 1; 84762306a36Sopenharmony_ci 84862306a36Sopenharmony_ci new_skb = sierra_net_skb_clone(dev, skb, hh.payload_len.word); 84962306a36Sopenharmony_ci if (new_skb) 85062306a36Sopenharmony_ci usbnet_skb_return(dev, new_skb); 85162306a36Sopenharmony_ci 85262306a36Sopenharmony_ci } /* while */ 85362306a36Sopenharmony_ci 85462306a36Sopenharmony_ci return 0; 85562306a36Sopenharmony_ci} 85662306a36Sopenharmony_ci 85762306a36Sopenharmony_ci/* ---------------------------- Transmit data path ----------------------*/ 85862306a36Sopenharmony_cistatic struct sk_buff *sierra_net_tx_fixup(struct usbnet *dev, 85962306a36Sopenharmony_ci struct sk_buff *skb, gfp_t flags) 86062306a36Sopenharmony_ci{ 86162306a36Sopenharmony_ci struct sierra_net_data *priv = sierra_net_get_private(dev); 86262306a36Sopenharmony_ci u16 len; 86362306a36Sopenharmony_ci bool need_tail; 86462306a36Sopenharmony_ci 86562306a36Sopenharmony_ci BUILD_BUG_ON(sizeof_field(struct usbnet, data) 86662306a36Sopenharmony_ci < sizeof(struct cdc_state)); 86762306a36Sopenharmony_ci 86862306a36Sopenharmony_ci dev_dbg(&dev->udev->dev, "%s", __func__); 86962306a36Sopenharmony_ci if (priv->link_up && check_ethip_packet(skb, dev) && is_ip(skb)) { 87062306a36Sopenharmony_ci /* enough head room as is? */ 87162306a36Sopenharmony_ci if (SIERRA_NET_HIP_EXT_HDR_LEN <= skb_headroom(skb)) { 87262306a36Sopenharmony_ci /* Save the Eth/IP length and set up HIP hdr */ 87362306a36Sopenharmony_ci len = skb->len; 87462306a36Sopenharmony_ci skb_push(skb, SIERRA_NET_HIP_EXT_HDR_LEN); 87562306a36Sopenharmony_ci /* Handle ZLP issue */ 87662306a36Sopenharmony_ci need_tail = ((len + SIERRA_NET_HIP_EXT_HDR_LEN) 87762306a36Sopenharmony_ci % dev->maxpacket == 0); 87862306a36Sopenharmony_ci if (need_tail) { 87962306a36Sopenharmony_ci if (unlikely(skb_tailroom(skb) == 0)) { 88062306a36Sopenharmony_ci netdev_err(dev->net, "tx_fixup:" 88162306a36Sopenharmony_ci "no room for packet\n"); 88262306a36Sopenharmony_ci dev_kfree_skb_any(skb); 88362306a36Sopenharmony_ci return NULL; 88462306a36Sopenharmony_ci } else { 88562306a36Sopenharmony_ci skb->data[skb->len] = 0; 88662306a36Sopenharmony_ci __skb_put(skb, 1); 88762306a36Sopenharmony_ci len = len + 1; 88862306a36Sopenharmony_ci } 88962306a36Sopenharmony_ci } 89062306a36Sopenharmony_ci build_hip(skb->data, len, priv); 89162306a36Sopenharmony_ci return skb; 89262306a36Sopenharmony_ci } else { 89362306a36Sopenharmony_ci /* 89462306a36Sopenharmony_ci * compensate in the future if necessary 89562306a36Sopenharmony_ci */ 89662306a36Sopenharmony_ci netdev_err(dev->net, "tx_fixup: no room for HIP\n"); 89762306a36Sopenharmony_ci } /* headroom */ 89862306a36Sopenharmony_ci } 89962306a36Sopenharmony_ci 90062306a36Sopenharmony_ci if (!priv->link_up) 90162306a36Sopenharmony_ci dev->net->stats.tx_carrier_errors++; 90262306a36Sopenharmony_ci 90362306a36Sopenharmony_ci /* tx_dropped incremented by usbnet */ 90462306a36Sopenharmony_ci 90562306a36Sopenharmony_ci /* filter the packet out, release it */ 90662306a36Sopenharmony_ci dev_kfree_skb_any(skb); 90762306a36Sopenharmony_ci return NULL; 90862306a36Sopenharmony_ci} 90962306a36Sopenharmony_ci 91062306a36Sopenharmony_cistatic const struct driver_info sierra_net_info_direct_ip = { 91162306a36Sopenharmony_ci .description = "Sierra Wireless USB-to-WWAN Modem", 91262306a36Sopenharmony_ci .flags = FLAG_WWAN | FLAG_SEND_ZLP, 91362306a36Sopenharmony_ci .bind = sierra_net_bind, 91462306a36Sopenharmony_ci .unbind = sierra_net_unbind, 91562306a36Sopenharmony_ci .status = sierra_net_status, 91662306a36Sopenharmony_ci .rx_fixup = sierra_net_rx_fixup, 91762306a36Sopenharmony_ci .tx_fixup = sierra_net_tx_fixup, 91862306a36Sopenharmony_ci}; 91962306a36Sopenharmony_ci 92062306a36Sopenharmony_cistatic int 92162306a36Sopenharmony_cisierra_net_probe(struct usb_interface *udev, const struct usb_device_id *prod) 92262306a36Sopenharmony_ci{ 92362306a36Sopenharmony_ci int ret; 92462306a36Sopenharmony_ci 92562306a36Sopenharmony_ci ret = usbnet_probe(udev, prod); 92662306a36Sopenharmony_ci if (ret == 0) { 92762306a36Sopenharmony_ci struct usbnet *dev = usb_get_intfdata(udev); 92862306a36Sopenharmony_ci 92962306a36Sopenharmony_ci ret = usbnet_status_start(dev, GFP_KERNEL); 93062306a36Sopenharmony_ci if (ret == 0) { 93162306a36Sopenharmony_ci /* Interrupt URB now set up; initiate sync sequence */ 93262306a36Sopenharmony_ci sierra_net_dosync(dev); 93362306a36Sopenharmony_ci } 93462306a36Sopenharmony_ci } 93562306a36Sopenharmony_ci return ret; 93662306a36Sopenharmony_ci} 93762306a36Sopenharmony_ci 93862306a36Sopenharmony_ci#define DIRECT_IP_DEVICE(vend, prod) \ 93962306a36Sopenharmony_ci {USB_DEVICE_INTERFACE_NUMBER(vend, prod, 7), \ 94062306a36Sopenharmony_ci .driver_info = (unsigned long)&sierra_net_info_direct_ip}, \ 94162306a36Sopenharmony_ci {USB_DEVICE_INTERFACE_NUMBER(vend, prod, 10), \ 94262306a36Sopenharmony_ci .driver_info = (unsigned long)&sierra_net_info_direct_ip}, \ 94362306a36Sopenharmony_ci {USB_DEVICE_INTERFACE_NUMBER(vend, prod, 11), \ 94462306a36Sopenharmony_ci .driver_info = (unsigned long)&sierra_net_info_direct_ip} 94562306a36Sopenharmony_ci 94662306a36Sopenharmony_cistatic const struct usb_device_id products[] = { 94762306a36Sopenharmony_ci DIRECT_IP_DEVICE(0x1199, 0x68A3), /* Sierra Wireless USB-to-WWAN modem */ 94862306a36Sopenharmony_ci DIRECT_IP_DEVICE(0x0F3D, 0x68A3), /* AT&T Direct IP modem */ 94962306a36Sopenharmony_ci DIRECT_IP_DEVICE(0x1199, 0x68AA), /* Sierra Wireless Direct IP LTE modem */ 95062306a36Sopenharmony_ci DIRECT_IP_DEVICE(0x0F3D, 0x68AA), /* AT&T Direct IP LTE modem */ 95162306a36Sopenharmony_ci 95262306a36Sopenharmony_ci {}, /* last item */ 95362306a36Sopenharmony_ci}; 95462306a36Sopenharmony_ciMODULE_DEVICE_TABLE(usb, products); 95562306a36Sopenharmony_ci 95662306a36Sopenharmony_ci/* We are based on usbnet, so let it handle the USB driver specifics */ 95762306a36Sopenharmony_cistatic struct usb_driver sierra_net_driver = { 95862306a36Sopenharmony_ci .name = "sierra_net", 95962306a36Sopenharmony_ci .id_table = products, 96062306a36Sopenharmony_ci .probe = sierra_net_probe, 96162306a36Sopenharmony_ci .disconnect = usbnet_disconnect, 96262306a36Sopenharmony_ci .suspend = usbnet_suspend, 96362306a36Sopenharmony_ci .resume = usbnet_resume, 96462306a36Sopenharmony_ci .no_dynamic_id = 1, 96562306a36Sopenharmony_ci .disable_hub_initiated_lpm = 1, 96662306a36Sopenharmony_ci}; 96762306a36Sopenharmony_ci 96862306a36Sopenharmony_cimodule_usb_driver(sierra_net_driver); 96962306a36Sopenharmony_ci 97062306a36Sopenharmony_ciMODULE_AUTHOR(DRIVER_AUTHOR); 97162306a36Sopenharmony_ciMODULE_DESCRIPTION(DRIVER_DESC); 97262306a36Sopenharmony_ciMODULE_VERSION(DRIVER_VERSION); 97362306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 974