Merge branch 'sierra_net-fixes'

Stefan Brüns says:

====================
Fixes for sierra_net driver

When trying to initiate a dual-stack (ipv4v6) connection, a MC7710, FW
version SWI9200X_03.05.24.00ap answers with an unsupported LSI. Add support
for this LSI.
Also the link_type should be ignored when going idle, otherwise the modem
is stuck in a bad link state.
Tested on MC7710, T-Mobile DE, APN internet.telekom, IPv4v6 PDP type. Both
IPv4 and IPv6 connections work.

v2: Do not overwrite protocol field in rx_fixup
v3: Remove leftover struct ethhdr *eth declaration
====================

Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
David S. Miller 2017-02-09 16:41:43 -05:00
commit 1b5805c29c

View File

@ -73,8 +73,6 @@ static atomic_t iface_counter = ATOMIC_INIT(0);
/* Private data structure */ /* Private data structure */
struct sierra_net_data { struct sierra_net_data {
u8 ethr_hdr_tmpl[ETH_HLEN]; /* ethernet header template for rx'd pkts */
u16 link_up; /* air link up or down */ u16 link_up; /* air link up or down */
u8 tx_hdr_template[4]; /* part of HIP hdr for tx'd packets */ u8 tx_hdr_template[4]; /* part of HIP hdr for tx'd packets */
@ -122,6 +120,7 @@ struct param {
/* LSI Protocol types */ /* LSI Protocol types */
#define SIERRA_NET_PROTOCOL_UMTS 0x01 #define SIERRA_NET_PROTOCOL_UMTS 0x01
#define SIERRA_NET_PROTOCOL_UMTS_DS 0x04
/* LSI Coverage */ /* LSI Coverage */
#define SIERRA_NET_COVERAGE_NONE 0x00 #define SIERRA_NET_COVERAGE_NONE 0x00
#define SIERRA_NET_COVERAGE_NOPACKET 0x01 #define SIERRA_NET_COVERAGE_NOPACKET 0x01
@ -129,7 +128,8 @@ struct param {
/* LSI Session */ /* LSI Session */
#define SIERRA_NET_SESSION_IDLE 0x00 #define SIERRA_NET_SESSION_IDLE 0x00
/* LSI Link types */ /* LSI Link types */
#define SIERRA_NET_AS_LINK_TYPE_IPv4 0x00 #define SIERRA_NET_AS_LINK_TYPE_IPV4 0x00
#define SIERRA_NET_AS_LINK_TYPE_IPV6 0x02
struct lsi_umts { struct lsi_umts {
u8 protocol; u8 protocol;
@ -137,9 +137,14 @@ struct lsi_umts {
__be16 length; __be16 length;
/* eventually use a union for the rest - assume umts for now */ /* eventually use a union for the rest - assume umts for now */
u8 coverage; u8 coverage;
u8 unused2[41]; u8 network_len; /* network name len */
u8 network[40]; /* network name (UCS2, bigendian) */
u8 session_state; u8 session_state;
u8 unused3[33]; u8 unused3[33];
} __packed;
struct lsi_umts_single {
struct lsi_umts lsi;
u8 link_type; u8 link_type;
u8 pdp_addr_len; /* NW-supplied PDP address len */ u8 pdp_addr_len; /* NW-supplied PDP address len */
u8 pdp_addr[16]; /* NW-supplied PDP address (bigendian)) */ u8 pdp_addr[16]; /* NW-supplied PDP address (bigendian)) */
@ -158,10 +163,31 @@ struct lsi_umts {
u8 reserved[8]; u8 reserved[8];
} __packed; } __packed;
struct lsi_umts_dual {
struct lsi_umts lsi;
u8 pdp_addr4_len; /* NW-supplied PDP IPv4 address len */
u8 pdp_addr4[4]; /* NW-supplied PDP IPv4 address (bigendian)) */
u8 pdp_addr6_len; /* NW-supplied PDP IPv6 address len */
u8 pdp_addr6[16]; /* NW-supplied PDP IPv6 address (bigendian)) */
u8 unused4[23];
u8 dns1_addr4_len; /* NW-supplied 1st DNS v4 address len (bigendian) */
u8 dns1_addr4[4]; /* NW-supplied 1st DNS v4 address */
u8 dns1_addr6_len; /* NW-supplied 1st DNS v6 address len */
u8 dns1_addr6[16]; /* NW-supplied 1st DNS v6 address (bigendian)*/
u8 dns2_addr4_len; /* NW-supplied 2nd DNS v4 address len (bigendian) */
u8 dns2_addr4[4]; /* NW-supplied 2nd DNS v4 address */
u8 dns2_addr6_len; /* NW-supplied 2nd DNS v6 address len */
u8 dns2_addr6[16]; /* NW-supplied 2nd DNS v6 address (bigendian)*/
u8 unused5[68];
} __packed;
#define SIERRA_NET_LSI_COMMON_LEN 4 #define SIERRA_NET_LSI_COMMON_LEN 4
#define SIERRA_NET_LSI_UMTS_LEN (sizeof(struct lsi_umts)) #define SIERRA_NET_LSI_UMTS_LEN (sizeof(struct lsi_umts_single))
#define SIERRA_NET_LSI_UMTS_STATUS_LEN \ #define SIERRA_NET_LSI_UMTS_STATUS_LEN \
(SIERRA_NET_LSI_UMTS_LEN - SIERRA_NET_LSI_COMMON_LEN) (SIERRA_NET_LSI_UMTS_LEN - SIERRA_NET_LSI_COMMON_LEN)
#define SIERRA_NET_LSI_UMTS_DS_LEN (sizeof(struct lsi_umts_dual))
#define SIERRA_NET_LSI_UMTS_DS_STATUS_LEN \
(SIERRA_NET_LSI_UMTS_DS_LEN - SIERRA_NET_LSI_COMMON_LEN)
/* Forward definitions */ /* Forward definitions */
static void sierra_sync_timer(unsigned long syncdata); static void sierra_sync_timer(unsigned long syncdata);
@ -190,10 +216,11 @@ static inline void sierra_net_set_private(struct usbnet *dev,
dev->data[0] = (unsigned long)priv; dev->data[0] = (unsigned long)priv;
} }
/* is packet IPv4 */ /* is packet IPv4/IPv6 */
static inline int is_ip(struct sk_buff *skb) static inline int is_ip(struct sk_buff *skb)
{ {
return skb->protocol == cpu_to_be16(ETH_P_IP); return skb->protocol == cpu_to_be16(ETH_P_IP) ||
skb->protocol == cpu_to_be16(ETH_P_IPV6);
} }
/* /*
@ -349,46 +376,51 @@ static inline int sierra_net_is_valid_addrlen(u8 len)
static int sierra_net_parse_lsi(struct usbnet *dev, char *data, int datalen) static int sierra_net_parse_lsi(struct usbnet *dev, char *data, int datalen)
{ {
struct lsi_umts *lsi = (struct lsi_umts *)data; struct lsi_umts *lsi = (struct lsi_umts *)data;
u32 expected_length;
if (datalen < sizeof(struct lsi_umts)) { if (datalen < sizeof(struct lsi_umts_single)) {
netdev_err(dev->net, "%s: Data length %d, exp %Zu\n", netdev_err(dev->net, "%s: Data length %d, exp >= %Zu\n",
__func__, datalen, __func__, datalen, sizeof(struct lsi_umts_single));
sizeof(struct lsi_umts));
return -1; return -1;
} }
if (lsi->length != cpu_to_be16(SIERRA_NET_LSI_UMTS_STATUS_LEN)) {
netdev_err(dev->net, "%s: LSI_UMTS_STATUS_LEN %d, exp %u\n",
__func__, be16_to_cpu(lsi->length),
(u32)SIERRA_NET_LSI_UMTS_STATUS_LEN);
return -1;
}
/* Validate the protocol - only support UMTS for now */
if (lsi->protocol != SIERRA_NET_PROTOCOL_UMTS) {
netdev_err(dev->net, "Protocol unsupported, 0x%02x\n",
lsi->protocol);
return -1;
}
/* Validate the link type */
if (lsi->link_type != SIERRA_NET_AS_LINK_TYPE_IPv4) {
netdev_err(dev->net, "Link type unsupported: 0x%02x\n",
lsi->link_type);
return -1;
}
/* Validate the coverage */
if (lsi->coverage == SIERRA_NET_COVERAGE_NONE
|| lsi->coverage == SIERRA_NET_COVERAGE_NOPACKET) {
netdev_err(dev->net, "No coverage, 0x%02x\n", lsi->coverage);
return 0;
}
/* Validate the session state */ /* Validate the session state */
if (lsi->session_state == SIERRA_NET_SESSION_IDLE) { if (lsi->session_state == SIERRA_NET_SESSION_IDLE) {
netdev_err(dev->net, "Session idle, 0x%02x\n", netdev_err(dev->net, "Session idle, 0x%02x\n",
lsi->session_state); 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;
} else if (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;
}
if (be16_to_cpu(lsi->length) != expected_length) {
netdev_err(dev->net, "%s: LSI_UMTS_STATUS_LEN %d, exp %u\n",
__func__, be16_to_cpu(lsi->length), expected_length);
return -1;
}
/* Validate the coverage */
if (lsi->coverage == SIERRA_NET_COVERAGE_NONE ||
lsi->coverage == SIERRA_NET_COVERAGE_NOPACKET) {
netdev_err(dev->net, "No coverage, 0x%02x\n", lsi->coverage);
return 0; return 0;
} }
@ -652,7 +684,6 @@ static int sierra_net_bind(struct usbnet *dev, struct usb_interface *intf)
u8 numendpoints; u8 numendpoints;
u16 fwattr = 0; u16 fwattr = 0;
int status; int status;
struct ethhdr *eth;
struct sierra_net_data *priv; struct sierra_net_data *priv;
static const u8 sync_tmplate[sizeof(priv->sync_msg)] = { static const u8 sync_tmplate[sizeof(priv->sync_msg)] = {
0x00, 0x00, SIERRA_NET_HIP_MSYNC_ID, 0x00}; 0x00, 0x00, SIERRA_NET_HIP_MSYNC_ID, 0x00};
@ -690,11 +721,6 @@ static int sierra_net_bind(struct usbnet *dev, struct usb_interface *intf)
dev->net->dev_addr[ETH_ALEN-2] = atomic_inc_return(&iface_counter); dev->net->dev_addr[ETH_ALEN-2] = atomic_inc_return(&iface_counter);
dev->net->dev_addr[ETH_ALEN-1] = ifacenum; dev->net->dev_addr[ETH_ALEN-1] = ifacenum;
/* we will have to manufacture ethernet headers, prepare template */
eth = (struct ethhdr *)priv->ethr_hdr_tmpl;
memcpy(&eth->h_dest, dev->net->dev_addr, ETH_ALEN);
eth->h_proto = cpu_to_be16(ETH_P_IP);
/* prepare shutdown message template */ /* prepare shutdown message template */
memcpy(priv->shdwn_msg, shdwn_tmplate, sizeof(priv->shdwn_msg)); memcpy(priv->shdwn_msg, shdwn_tmplate, sizeof(priv->shdwn_msg));
/* set context index initially to 0 - prepares tx hdr template */ /* set context index initially to 0 - prepares tx hdr template */
@ -824,9 +850,14 @@ static int sierra_net_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
skb_pull(skb, hh.hdrlen); skb_pull(skb, hh.hdrlen);
/* We are going to accept this packet, prepare it */ /* We are going to accept this packet, prepare it.
memcpy(skb->data, sierra_net_get_private(dev)->ethr_hdr_tmpl, * In case protocol is IPv6, keep it, otherwise force IPv4.
ETH_HLEN); */
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 */ /* Last packet in batch handled by usbnet */
if (hh.payload_len.word == skb->len) if (hh.payload_len.word == skb->len)