diff options
Diffstat (limited to 'drivers/net/usb/cdc_mbim.c')
-rw-r--r-- | drivers/net/usb/cdc_mbim.c | 104 |
1 files changed, 82 insertions, 22 deletions
diff --git a/drivers/net/usb/cdc_mbim.c b/drivers/net/usb/cdc_mbim.c index 25ba7eca9a13..c9f3281506af 100644 --- a/drivers/net/usb/cdc_mbim.c +++ b/drivers/net/usb/cdc_mbim.c | |||
@@ -21,6 +21,8 @@ | |||
21 | #include <linux/usb/usbnet.h> | 21 | #include <linux/usb/usbnet.h> |
22 | #include <linux/usb/cdc-wdm.h> | 22 | #include <linux/usb/cdc-wdm.h> |
23 | #include <linux/usb/cdc_ncm.h> | 23 | #include <linux/usb/cdc_ncm.h> |
24 | #include <net/ipv6.h> | ||
25 | #include <net/addrconf.h> | ||
24 | 26 | ||
25 | /* driver specific data - must match cdc_ncm usage */ | 27 | /* driver specific data - must match cdc_ncm usage */ |
26 | struct cdc_mbim_state { | 28 | struct cdc_mbim_state { |
@@ -42,13 +44,11 @@ static int cdc_mbim_manage_power(struct usbnet *dev, int on) | |||
42 | if ((on && atomic_add_return(1, &info->pmcount) == 1) || (!on && atomic_dec_and_test(&info->pmcount))) { | 44 | if ((on && atomic_add_return(1, &info->pmcount) == 1) || (!on && atomic_dec_and_test(&info->pmcount))) { |
43 | /* need autopm_get/put here to ensure the usbcore sees the new value */ | 45 | /* need autopm_get/put here to ensure the usbcore sees the new value */ |
44 | rv = usb_autopm_get_interface(dev->intf); | 46 | rv = usb_autopm_get_interface(dev->intf); |
45 | if (rv < 0) | ||
46 | goto err; | ||
47 | dev->intf->needs_remote_wakeup = on; | 47 | dev->intf->needs_remote_wakeup = on; |
48 | usb_autopm_put_interface(dev->intf); | 48 | if (!rv) |
49 | usb_autopm_put_interface(dev->intf); | ||
49 | } | 50 | } |
50 | err: | 51 | return 0; |
51 | return rv; | ||
52 | } | 52 | } |
53 | 53 | ||
54 | static int cdc_mbim_wdm_manage_power(struct usb_interface *intf, int status) | 54 | static int cdc_mbim_wdm_manage_power(struct usb_interface *intf, int status) |
@@ -173,7 +173,7 @@ static struct sk_buff *cdc_mbim_tx_fixup(struct usbnet *dev, struct sk_buff *skb | |||
173 | } | 173 | } |
174 | 174 | ||
175 | spin_lock_bh(&ctx->mtx); | 175 | spin_lock_bh(&ctx->mtx); |
176 | skb_out = cdc_ncm_fill_tx_frame(ctx, skb, sign); | 176 | skb_out = cdc_ncm_fill_tx_frame(dev, skb, sign); |
177 | spin_unlock_bh(&ctx->mtx); | 177 | spin_unlock_bh(&ctx->mtx); |
178 | return skb_out; | 178 | return skb_out; |
179 | 179 | ||
@@ -184,6 +184,60 @@ error: | |||
184 | return NULL; | 184 | return NULL; |
185 | } | 185 | } |
186 | 186 | ||
187 | /* Some devices are known to send Neigbor Solicitation messages and | ||
188 | * require Neigbor Advertisement replies. The IPv6 core will not | ||
189 | * respond since IFF_NOARP is set, so we must handle them ourselves. | ||
190 | */ | ||
191 | static void do_neigh_solicit(struct usbnet *dev, u8 *buf, u16 tci) | ||
192 | { | ||
193 | struct ipv6hdr *iph = (void *)buf; | ||
194 | struct nd_msg *msg = (void *)(iph + 1); | ||
195 | struct net_device *netdev; | ||
196 | struct inet6_dev *in6_dev; | ||
197 | bool is_router; | ||
198 | |||
199 | /* we'll only respond to requests from unicast addresses to | ||
200 | * our solicited node addresses. | ||
201 | */ | ||
202 | if (!ipv6_addr_is_solict_mult(&iph->daddr) || | ||
203 | !(ipv6_addr_type(&iph->saddr) & IPV6_ADDR_UNICAST)) | ||
204 | return; | ||
205 | |||
206 | /* need to send the NA on the VLAN dev, if any */ | ||
207 | if (tci) | ||
208 | netdev = __vlan_find_dev_deep(dev->net, htons(ETH_P_8021Q), | ||
209 | tci); | ||
210 | else | ||
211 | netdev = dev->net; | ||
212 | if (!netdev) | ||
213 | return; | ||
214 | |||
215 | in6_dev = in6_dev_get(netdev); | ||
216 | if (!in6_dev) | ||
217 | return; | ||
218 | is_router = !!in6_dev->cnf.forwarding; | ||
219 | in6_dev_put(in6_dev); | ||
220 | |||
221 | /* ipv6_stub != NULL if in6_dev_get returned an inet6_dev */ | ||
222 | ipv6_stub->ndisc_send_na(netdev, NULL, &iph->saddr, &msg->target, | ||
223 | is_router /* router */, | ||
224 | true /* solicited */, | ||
225 | false /* override */, | ||
226 | true /* inc_opt */); | ||
227 | } | ||
228 | |||
229 | static bool is_neigh_solicit(u8 *buf, size_t len) | ||
230 | { | ||
231 | struct ipv6hdr *iph = (void *)buf; | ||
232 | struct nd_msg *msg = (void *)(iph + 1); | ||
233 | |||
234 | return (len >= sizeof(struct ipv6hdr) + sizeof(struct nd_msg) && | ||
235 | iph->nexthdr == IPPROTO_ICMPV6 && | ||
236 | msg->icmph.icmp6_code == 0 && | ||
237 | msg->icmph.icmp6_type == NDISC_NEIGHBOUR_SOLICITATION); | ||
238 | } | ||
239 | |||
240 | |||
187 | static struct sk_buff *cdc_mbim_process_dgram(struct usbnet *dev, u8 *buf, size_t len, u16 tci) | 241 | static struct sk_buff *cdc_mbim_process_dgram(struct usbnet *dev, u8 *buf, size_t len, u16 tci) |
188 | { | 242 | { |
189 | __be16 proto = htons(ETH_P_802_3); | 243 | __be16 proto = htons(ETH_P_802_3); |
@@ -198,6 +252,8 @@ static struct sk_buff *cdc_mbim_process_dgram(struct usbnet *dev, u8 *buf, size_ | |||
198 | proto = htons(ETH_P_IP); | 252 | proto = htons(ETH_P_IP); |
199 | break; | 253 | break; |
200 | case 0x60: | 254 | case 0x60: |
255 | if (is_neigh_solicit(buf, len)) | ||
256 | do_neigh_solicit(dev, buf, tci); | ||
201 | proto = htons(ETH_P_IPV6); | 257 | proto = htons(ETH_P_IPV6); |
202 | break; | 258 | break; |
203 | default: | 259 | default: |
@@ -313,15 +369,13 @@ error: | |||
313 | 369 | ||
314 | static int cdc_mbim_suspend(struct usb_interface *intf, pm_message_t message) | 370 | static int cdc_mbim_suspend(struct usb_interface *intf, pm_message_t message) |
315 | { | 371 | { |
316 | int ret = 0; | 372 | int ret = -ENODEV; |
317 | struct usbnet *dev = usb_get_intfdata(intf); | 373 | struct usbnet *dev = usb_get_intfdata(intf); |
318 | struct cdc_mbim_state *info = (void *)&dev->data; | 374 | struct cdc_mbim_state *info = (void *)&dev->data; |
319 | struct cdc_ncm_ctx *ctx = info->ctx; | 375 | struct cdc_ncm_ctx *ctx = info->ctx; |
320 | 376 | ||
321 | if (ctx == NULL) { | 377 | if (!ctx) |
322 | ret = -1; | ||
323 | goto error; | 378 | goto error; |
324 | } | ||
325 | 379 | ||
326 | /* | 380 | /* |
327 | * Both usbnet_suspend() and subdriver->suspend() MUST return 0 | 381 | * Both usbnet_suspend() and subdriver->suspend() MUST return 0 |
@@ -354,7 +408,7 @@ static int cdc_mbim_resume(struct usb_interface *intf) | |||
354 | if (ret < 0) | 408 | if (ret < 0) |
355 | goto err; | 409 | goto err; |
356 | ret = usbnet_resume(intf); | 410 | ret = usbnet_resume(intf); |
357 | if (ret < 0 && callsub && info->subdriver->suspend) | 411 | if (ret < 0 && callsub) |
358 | info->subdriver->suspend(intf, PMSG_SUSPEND); | 412 | info->subdriver->suspend(intf, PMSG_SUSPEND); |
359 | err: | 413 | err: |
360 | return ret; | 414 | return ret; |
@@ -371,9 +425,18 @@ static const struct driver_info cdc_mbim_info = { | |||
371 | }; | 425 | }; |
372 | 426 | ||
373 | /* MBIM and NCM devices should not need a ZLP after NTBs with | 427 | /* MBIM and NCM devices should not need a ZLP after NTBs with |
374 | * dwNtbOutMaxSize length. This driver_info is for the exceptional | 428 | * dwNtbOutMaxSize length. Nevertheless, a number of devices from |
375 | * devices requiring it anyway, allowing them to be supported without | 429 | * different vendor IDs will fail unless we send ZLPs, forcing us |
376 | * forcing the performance penalty on all the sane devices. | 430 | * to make this the default. |
431 | * | ||
432 | * This default may cause a performance penalty for spec conforming | ||
433 | * devices wanting to take advantage of optimizations possible without | ||
434 | * ZLPs. A whitelist is added in an attempt to avoid this for devices | ||
435 | * known to conform to the MBIM specification. | ||
436 | * | ||
437 | * All known devices supporting NCM compatibility mode are also | ||
438 | * conforming to the NCM and MBIM specifications. For this reason, the | ||
439 | * NCM subclass entry is also in the ZLP whitelist. | ||
377 | */ | 440 | */ |
378 | static const struct driver_info cdc_mbim_info_zlp = { | 441 | static const struct driver_info cdc_mbim_info_zlp = { |
379 | .description = "CDC MBIM", | 442 | .description = "CDC MBIM", |
@@ -396,16 +459,13 @@ static const struct usb_device_id mbim_devs[] = { | |||
396 | { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_NCM, USB_CDC_PROTO_NONE), | 459 | { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_NCM, USB_CDC_PROTO_NONE), |
397 | .driver_info = (unsigned long)&cdc_mbim_info, | 460 | .driver_info = (unsigned long)&cdc_mbim_info, |
398 | }, | 461 | }, |
399 | /* Sierra Wireless MC7710 need ZLPs */ | 462 | /* ZLP conformance whitelist: All Ericsson MBIM devices */ |
400 | { USB_DEVICE_AND_INTERFACE_INFO(0x1199, 0x68a2, USB_CLASS_COMM, USB_CDC_SUBCLASS_MBIM, USB_CDC_PROTO_NONE), | 463 | { USB_VENDOR_AND_INTERFACE_INFO(0x0bdb, USB_CLASS_COMM, USB_CDC_SUBCLASS_MBIM, USB_CDC_PROTO_NONE), |
401 | .driver_info = (unsigned long)&cdc_mbim_info_zlp, | 464 | .driver_info = (unsigned long)&cdc_mbim_info, |
402 | }, | ||
403 | /* HP hs2434 Mobile Broadband Module needs ZLPs */ | ||
404 | { USB_DEVICE_AND_INTERFACE_INFO(0x3f0, 0x4b1d, USB_CLASS_COMM, USB_CDC_SUBCLASS_MBIM, USB_CDC_PROTO_NONE), | ||
405 | .driver_info = (unsigned long)&cdc_mbim_info_zlp, | ||
406 | }, | 465 | }, |
466 | /* default entry */ | ||
407 | { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_MBIM, USB_CDC_PROTO_NONE), | 467 | { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_MBIM, USB_CDC_PROTO_NONE), |
408 | .driver_info = (unsigned long)&cdc_mbim_info, | 468 | .driver_info = (unsigned long)&cdc_mbim_info_zlp, |
409 | }, | 469 | }, |
410 | { | 470 | { |
411 | }, | 471 | }, |