// SPDX-License-Identifier: GPL-2.0-or-later /* * USB-to-WWAN Driver for Sierra Wireless modems * * Copyright (C) 2008, 2009, 2010 Paxton Smith, Matthew Safar, Rory Filer * <linux@sierrawireless.com> * * Portions of this based on the cdc_ether driver by David Brownell (2003-2005) * and Ole Andre Vadla Ravnas (ActiveSync) (2006). * * IMPORTANT DISCLAIMER: This driver is not commercially supported by * Sierra Wireless. Use at your own risk.
*/
#define DRIVER_VERSION "v.2.0" #define DRIVER_AUTHOR "Paxton Smith, Matthew Safar, Rory Filer" #define DRIVER_DESC "USB-to-WWAN Driver for Sierra Wireless modems" staticconstchar driver_name[] = "sierra_net";
/* if defined debug messages enabled */ /*#define DEBUG*/
/* atomic counter partially included in MAC address to make sure 2 devices * do not end up with the same MAC - concept breaks in case of > 255 ifaces
*/ static atomic_t iface_counter = ATOMIC_INIT(0);
/* * SYNC Timer Delay definition used to set the expiry time
*/ #define SIERRA_NET_SYNCDELAY (2*HZ)
/* Max. MTU supported. The modem buffers are limited to 1500 */ #define SIERRA_NET_MAX_SUPPORTED_MTU 1500
/* The SIERRA_NET_USBCTL_BUF_LEN defines a buffer size allocated for control * message reception ... and thus the max. received packet. * (May be the cause for parse_hip returning -EINVAL)
*/ #define SIERRA_NET_USBCTL_BUF_LEN 1024
/* Backpointer to the container */ struct usbnet *usbnet;
u8 ifnum; /* interface number */
/* Bit masks, must be a power of 2 */ #define SIERRA_NET_EVENT_RESP_AVAIL 0x01 #define SIERRA_NET_TIMER_EXPIRY 0x02 unsignedlong kevent_flags; struct work_struct sierra_net_kevent; struct timer_list sync_timer; /* For retrying SYNC sequence */
};
struct param { int is_present; union { void *ptr;
u32 dword;
u16 word;
u8 byte;
};
};
struct lsi_umts {
u8 protocol;
u8 unused1;
__be16 length; /* eventually use a union for the rest - assume umts for now */
u8 coverage;
u8 network_len; /* network name len */
u8 network[40]; /* network name (UCS2, bigendian) */
u8 session_state;
u8 unused3[33];
} __packed;
/* * check passed in packet and make sure that: * - it is linear (no scatter/gather) * - it is ethernet (mac_header properly set)
*/ staticint check_ethip_packet(struct sk_buff *skb, struct usbnet *dev)
{
skb_reset_mac_header(skb); /* ethernet header */
if (skb_is_nonlinear(skb)) {
netdev_err(dev->net, "Non linear buffer-dropping\n"); return 0;
}
if (!pskb_may_pull(skb, ETH_HLEN)) return 0;
skb->protocol = eth_hdr(skb)->h_proto;
if (padded) {
hh->hdrlen++;
hh->payload_len.word--;
}
/* if real packet shorter than the claimed length */ if (buflen < (hh->hdrlen + hh->payload_len.word)) return -EINVAL;
return 0;
}
staticvoid build_hip(u8 *buf, const u16 payloadlen, struct sierra_net_data *priv)
{ /* the following doesn't have the full functionality. We * currently build only one kind of header, so it is faster this way
*/
put_unaligned_be16(payloadlen, buf);
memcpy(buf+2, priv->tx_hdr_template, sizeof(priv->tx_hdr_template));
} /*----------------------------------------------------------------------------* * END HIP *
*----------------------------------------------------------------------------*/
staticint sierra_net_send_cmd(struct usbnet *dev,
u8 *cmd, int cmdlen, constchar * cmd_name)
{ struct sierra_net_data *priv = sierra_net_get_private(dev); int status;
status = usbnet_write_cmd(dev, USB_CDC_SEND_ENCAPSULATED_COMMAND,
USB_DIR_OUT|USB_TYPE_CLASS|USB_RECIP_INTERFACE,
0, priv->ifnum, cmd, cmdlen);
if (status != cmdlen && status != -ENODEV)
netdev_err(dev->net, "Submit %s failed %d\n", cmd_name, status);
/* Validate the session state */ if (lsi->session_state == SIERRA_NET_SESSION_IDLE) {
netdev_err(dev->net, "Session idle, 0x%02x\n",
lsi->session_state); return 0;
}
/* Validate the protocol - only support UMTS for now */ if (lsi->protocol == SIERRA_NET_PROTOCOL_UMTS) { struct lsi_umts_single *single = (struct lsi_umts_single *)lsi;
/* Validate the link type */ if (single->link_type != SIERRA_NET_AS_LINK_TYPE_IPV4 &&
single->link_type != SIERRA_NET_AS_LINK_TYPE_IPV6) {
netdev_err(dev->net, "Link type unsupported: 0x%02x\n",
single->link_type); return -1;
}
expected_length = SIERRA_NET_LSI_UMTS_STATUS_LEN;
} elseif (lsi->protocol == SIERRA_NET_PROTOCOL_UMTS_DS) {
expected_length = SIERRA_NET_LSI_UMTS_DS_STATUS_LEN;
} else {
netdev_err(dev->net, "Protocol unsupported, 0x%02x\n",
lsi->protocol); return -1;
}
/* The SIERRA_NET_HIP_MSYNC_ID command appears to request that the * firmware restart itself. After restarting, the modem will respond * with the SIERRA_NET_HIP_RESTART_ID indication. The driver continues * sending MSYNC commands every few seconds until it receives the * RESTART event from the firmware
*/
/* tell modem we are ready */
status = sierra_net_send_sync(dev); if (status < 0)
netdev_err(dev->net, "Send SYNC failed, status %d\n", status);
status = sierra_net_send_sync(dev); if (status < 0)
netdev_err(dev->net, "Send SYNC failed, status %d\n", status);
/* Now, start a timer and make sure we get the Restart Indication */
priv->sync_timer.expires = jiffies + SIERRA_NET_SYNCDELAY;
add_timer(&priv->sync_timer);
}
static u32 sierra_net_get_link(struct net_device *net)
{ struct usbnet *dev = netdev_priv(net); /* Report link is down whenever the interface is down */ return sierra_net_get_private(dev)->link_up && netif_running(net);
}
staticint sierra_net_get_fw_attr(struct usbnet *dev, u16 *datap)
{ int result = 0;
__le16 attrdata;
result = usbnet_read_cmd(dev, /* _u8 vendor specific request */
SWI_USB_REQUEST_GET_FW_ATTR,
USB_DIR_IN | USB_TYPE_VENDOR, /* __u8 request type */
0x0000, /* __u16 value not used */
0x0000, /* __u16 index not used */
&attrdata, /* char *data */ sizeof(attrdata) /* __u16 size */
);
ifacenum = intf->cur_altsetting->desc.bInterfaceNumber;
numendpoints = intf->cur_altsetting->desc.bNumEndpoints; /* We have three endpoints, bulk in and out, and a status */ if (numendpoints != 3) {
dev_err(&dev->udev->dev, "Expected 3 endpoints, found: %d",
numendpoints); return -ENODEV;
} /* Status endpoint set in usbnet_get_endpoints() */
dev->status = NULL;
status = usbnet_get_endpoints(dev, intf); if (status < 0) {
dev_err(&dev->udev->dev, "Error in usbnet_get_endpoints (%d)",
status); return -ENODEV;
} if (!dev->status) {
dev_err(&dev->udev->dev, "No status endpoint found"); return -ENODEV;
} /* Initialize sierra private data */
priv = kzalloc(sizeof *priv, GFP_KERNEL); if (!priv) return -ENOMEM;
/* change MAC addr to include, ifacenum, and to be unique */
mod[0] = atomic_inc_return(&iface_counter);
mod[1] = ifacenum;
dev_addr_mod(dev->net, ETH_ALEN - 2, mod, 2);
/* prepare shutdown message template */
memcpy(priv->shdwn_msg, shdwn_tmplate, sizeof(priv->shdwn_msg)); /* set context index initially to 0 - prepares tx hdr template */
sierra_net_set_ctx_index(priv, 0);
/* decrease the rx_urb_size and max_tx_size to 4k on USB 1.1 */
dev->rx_urb_size = SIERRA_NET_RX_URB_SIZE; if (dev->udev->speed != USB_SPEED_HIGH)
dev->rx_urb_size = min_t(size_t, 4096, SIERRA_NET_RX_URB_SIZE);
/* kill the timer and work */
timer_shutdown_sync(&priv->sync_timer);
cancel_work_sync(&priv->sierra_net_kevent);
/* tell modem we are going away */
status = sierra_net_send_cmd(dev, priv->shdwn_msg, sizeof(priv->shdwn_msg), "Shutdown"); if (status < 0)
netdev_err(dev->net, "usb_control_msg failed, status %d\n", status);
/* remove len bytes from original */
skb_pull(skb, len);
/* trim next packet to it's length */ if (new_skb) {
skb_trim(new_skb, len);
} else { if (netif_msg_rx_err(dev))
netdev_err(dev->net, "failed to get skb\n");
dev->net->stats.rx_dropped++;
}
/* could contain multiple packets */ while (likely(skb->len)) {
err = parse_hip(skb->data, skb->len, &hh); if (err) { if (netif_msg_rx_err(dev))
netdev_err(dev->net, "Invalid HIP header %d\n",
err); /* dev->net->stats.rx_errors incremented by caller */
dev->net->stats.rx_length_errors++; return 0;
}
/* Validate Extended HIP header */ if (!hh.extmsgid.is_present
|| hh.extmsgid.word != SIERRA_NET_HIP_EXT_IP_IN_ID) { if (netif_msg_rx_err(dev))
netdev_err(dev->net, "HIP/ETH: Invalid pkt\n");
dev->net->stats.rx_frame_errors++; /* dev->net->stats.rx_errors incremented by caller */ return 0;
}
skb_pull(skb, hh.hdrlen);
/* We are going to accept this packet, prepare it. * In case protocol is IPv6, keep it, otherwise force IPv4.
*/
skb_reset_mac_header(skb); if (eth_hdr(skb)->h_proto != cpu_to_be16(ETH_P_IPV6))
eth_hdr(skb)->h_proto = cpu_to_be16(ETH_P_IP);
eth_zero_addr(eth_hdr(skb)->h_source);
memcpy(eth_hdr(skb)->h_dest, dev->net->dev_addr, ETH_ALEN);
/* Last packet in batch handled by usbnet */ if (hh.payload_len.word == skb->len) return 1;
new_skb = sierra_net_skb_clone(dev, skb, hh.payload_len.word); if (new_skb)
usbnet_skb_return(dev, new_skb);
staticconststruct usb_device_id products[] = {
DIRECT_IP_DEVICE(0x1199, 0x68A3), /* Sierra Wireless USB-to-WWAN modem */
DIRECT_IP_DEVICE(0x0F3D, 0x68A3), /* AT&T Direct IP modem */
DIRECT_IP_DEVICE(0x1199, 0x68AA), /* Sierra Wireless Direct IP LTE modem */
DIRECT_IP_DEVICE(0x0F3D, 0x68AA), /* AT&T Direct IP LTE modem */
{}, /* last item */
};
MODULE_DEVICE_TABLE(usb, products);
/* We are based on usbnet, so let it handle the USB driver specifics */ staticstruct usb_driver sierra_net_driver = {
.name = "sierra_net",
.id_table = products,
.probe = sierra_net_probe,
.disconnect = usbnet_disconnect,
.suspend = usbnet_suspend,
.resume = usbnet_resume,
.no_dynamic_id = 1,
.disable_hub_initiated_lpm = 1,
};
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.