aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/net/usb/qmi_wwan.c
diff options
context:
space:
mode:
authorBjørn Mork <bjorn@mork.no>2012-06-18 20:42:01 -0400
committerDavid S. Miller <davem@davemloft.net>2012-06-19 18:04:14 -0400
commit230718bda1be24119d9e25623d90fa5da3079aa9 (patch)
treea1c4869bceccdb7fece54c39cd8ec45abf473568 /drivers/net/usb/qmi_wwan.c
parentf47cd1360f36e599815650522986673b9aa83393 (diff)
net: qmi_wwan: bind to both control and data interface
Always bind to control interface regardless of whether it is a shared interface or not. A QMI/wwan function is required to provide both a control interface (QMI) and a data interface (wwan). All devices supported by this driver do so. But the vendors may choose to use different USB descriptor layouts, and some vendors even allow the same device to present different layouts. Most of these devices use a USB descriptor layout with a single USB interface for both control and data. But some split control and data into two interfaces, bound together by a CDC Union descriptor on the control interface. Before the cdc-wdm subdriver support was added, this split was used to let cdc-wdm drive the QMI control interface and qmi_wwan drive the wwna data interface. This split driver model has a number of issues: - qmi_wwan must match on the data interface descriptor, which often are indistiguishable from data interfaces belonging to other CDC (like) functions like ACM - supporting a single QMI/wwan function requires adding the device to two drivers - syncronizing the probes among a number of drivers, to ensure selecting the correct driver, is difficult unless all drivers match on the same interface This patch resolves these problems by using the same probing mechanism as cdc-ether for devices with a two- interface USB descriptor layout. This makes the driver behave consistently, supporting both the control and data part of the QMI/wwan function, regardless of the USB descriptors. Cc: Thomas Schäfer <tschaefer@t-online.de> Signed-off-by: Bjørn Mork <bjorn@mork.no> Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'drivers/net/usb/qmi_wwan.c')
-rw-r--r--drivers/net/usb/qmi_wwan.c131
1 files changed, 63 insertions, 68 deletions
diff --git a/drivers/net/usb/qmi_wwan.c b/drivers/net/usb/qmi_wwan.c
index 6fcf54d43eab..05571fcbd70a 100644
--- a/drivers/net/usb/qmi_wwan.c
+++ b/drivers/net/usb/qmi_wwan.c
@@ -1,6 +1,10 @@
1/* 1/*
2 * Copyright (c) 2012 Bjørn Mork <bjorn@mork.no> 2 * Copyright (c) 2012 Bjørn Mork <bjorn@mork.no>
3 * 3 *
4 * The probing code is heavily inspired by cdc_ether, which is:
5 * Copyright (C) 2003-2005 by David Brownell
6 * Copyright (C) 2006 by Ole Andre Vadla Ravnas (ActiveSync)
7 *
4 * This program is free software; you can redistribute it and/or 8 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License 9 * modify it under the terms of the GNU General Public License
6 * version 2 as published by the Free Software Foundation. 10 * version 2 as published by the Free Software Foundation.
@@ -15,11 +19,7 @@
15#include <linux/usb/usbnet.h> 19#include <linux/usb/usbnet.h>
16#include <linux/usb/cdc-wdm.h> 20#include <linux/usb/cdc-wdm.h>
17 21
18/* The name of the CDC Device Management driver */ 22/* This driver supports wwan (3G/LTE/?) devices using a vendor
19#define DM_DRIVER "cdc_wdm"
20
21/*
22 * This driver supports wwan (3G/LTE/?) devices using a vendor
23 * specific management protocol called Qualcomm MSM Interface (QMI) - 23 * specific management protocol called Qualcomm MSM Interface (QMI) -
24 * in addition to the more common AT commands over serial interface 24 * in addition to the more common AT commands over serial interface
25 * management 25 * management
@@ -31,27 +31,16 @@
31 * management protocol is used in place of the standard CDC 31 * management protocol is used in place of the standard CDC
32 * notifications NOTIFY_NETWORK_CONNECTION and NOTIFY_SPEED_CHANGE 32 * notifications NOTIFY_NETWORK_CONNECTION and NOTIFY_SPEED_CHANGE
33 * 33 *
34 * Alternatively, control and data functions can be combined in a
35 * single USB interface.
36 *
34 * Handling a protocol like QMI is out of the scope for any driver. 37 * Handling a protocol like QMI is out of the scope for any driver.
35 * It can be exported as a character device using the cdc-wdm driver, 38 * It is exported as a character device using the cdc-wdm driver as
36 * which will enable userspace applications ("modem managers") to 39 * a subdriver, enabling userspace applications ("modem managers") to
37 * handle it. This may be required to use the network interface 40 * handle it.
38 * provided by the driver.
39 * 41 *
40 * These devices may alternatively/additionally be configured using AT 42 * These devices may alternatively/additionally be configured using AT
41 * commands on any of the serial interfaces driven by the option driver 43 * commands on a serial interface
42 *
43 * This driver binds only to the data ("slave") interface to enable
44 * the cdc-wdm driver to bind to the control interface. It still
45 * parses the CDC functional descriptors on the control interface to
46 * a) verify that this is indeed a handled interface (CDC Union
47 * header lists it as slave)
48 * b) get MAC address and other ethernet config from the CDC Ethernet
49 * header
50 * c) enable user bind requests against the control interface, which
51 * is the common way to bind to CDC Ethernet Control Model type
52 * interfaces
53 * d) provide a hint to the user about which interface is the
54 * corresponding management interface
55 */ 44 */
56 45
57/* driver specific data */ 46/* driver specific data */
@@ -135,7 +124,6 @@ err:
135static int qmi_wwan_bind(struct usbnet *dev, struct usb_interface *intf) 124static int qmi_wwan_bind(struct usbnet *dev, struct usb_interface *intf)
136{ 125{
137 int status = -1; 126 int status = -1;
138 struct usb_interface *control = NULL;
139 u8 *buf = intf->cur_altsetting->extra; 127 u8 *buf = intf->cur_altsetting->extra;
140 int len = intf->cur_altsetting->extralen; 128 int len = intf->cur_altsetting->extralen;
141 struct usb_interface_descriptor *desc = &intf->cur_altsetting->desc; 129 struct usb_interface_descriptor *desc = &intf->cur_altsetting->desc;
@@ -143,27 +131,14 @@ static int qmi_wwan_bind(struct usbnet *dev, struct usb_interface *intf)
143 struct usb_cdc_ether_desc *cdc_ether = NULL; 131 struct usb_cdc_ether_desc *cdc_ether = NULL;
144 u32 required = 1 << USB_CDC_HEADER_TYPE | 1 << USB_CDC_UNION_TYPE; 132 u32 required = 1 << USB_CDC_HEADER_TYPE | 1 << USB_CDC_UNION_TYPE;
145 u32 found = 0; 133 u32 found = 0;
134 struct usb_driver *driver = driver_of(intf);
146 struct qmi_wwan_state *info = (void *)&dev->data; 135 struct qmi_wwan_state *info = (void *)&dev->data;
147 136
148 BUILD_BUG_ON((sizeof(((struct usbnet *)0)->data) < sizeof(struct qmi_wwan_state))); 137 BUILD_BUG_ON((sizeof(((struct usbnet *)0)->data) < sizeof(struct qmi_wwan_state)));
149 138
150 atomic_set(&info->pmcount, 0); 139 /* require a single interrupt status endpoint for subdriver */
151 140 if (intf->cur_altsetting->desc.bNumEndpoints != 1)
152 /* 141 goto err;
153 * assume a data interface has no additional descriptors and
154 * that the control and data interface are numbered
155 * consecutively - this holds for the Huawei device at least
156 */
157 if (len == 0 && desc->bInterfaceNumber > 0) {
158 control = usb_ifnum_to_if(dev->udev, desc->bInterfaceNumber - 1);
159 if (!control)
160 goto err;
161
162 buf = control->cur_altsetting->extra;
163 len = control->cur_altsetting->extralen;
164 dev_dbg(&intf->dev, "guessing \"control\" => %s, \"data\" => this\n",
165 dev_name(&control->dev));
166 }
167 142
168 while (len > 3) { 143 while (len > 3) {
169 struct usb_descriptor_header *h = (void *)buf; 144 struct usb_descriptor_header *h = (void *)buf;
@@ -227,10 +202,17 @@ next_desc:
227 goto err; 202 goto err;
228 } 203 }
229 204
230 /* give the user a helpful hint if trying to bind to the wrong interface */ 205 /* verify CDC Union */
231 if (cdc_union && desc->bInterfaceNumber == cdc_union->bMasterInterface0) { 206 if (desc->bInterfaceNumber != cdc_union->bMasterInterface0) {
232 dev_err(&intf->dev, "leaving \"control\" interface for " DM_DRIVER " - try binding to %s instead!\n", 207 dev_err(&intf->dev, "bogus CDC Union: master=%u\n", cdc_union->bMasterInterface0);
233 dev_name(&usb_ifnum_to_if(dev->udev, cdc_union->bSlaveInterface0)->dev)); 208 goto err;
209 }
210
211 /* need to save these for unbind */
212 info->control = intf;
213 info->data = usb_ifnum_to_if(dev->udev, cdc_union->bSlaveInterface0);
214 if (!info->data) {
215 dev_err(&intf->dev, "bogus CDC Union: slave=%u\n", cdc_union->bSlaveInterface0);
234 goto err; 216 goto err;
235 } 217 }
236 218
@@ -240,15 +222,16 @@ next_desc:
240 usbnet_get_ethernet_addr(dev, cdc_ether->iMACAddress); 222 usbnet_get_ethernet_addr(dev, cdc_ether->iMACAddress);
241 } 223 }
242 224
243 /* success! point the user to the management interface */ 225 /* claim data interface and set it up */
244 if (control) 226 status = usb_driver_claim_interface(driver, info->data, dev);
245 dev_info(&intf->dev, "Use \"" DM_DRIVER "\" for QMI interface %s\n", 227 if (status < 0)
246 dev_name(&control->dev)); 228 goto err;
247
248 /* XXX: add a sysfs symlink somewhere to help management applications find it? */
249 229
250 /* collect bulk endpoints now that we know intf == "data" interface */ 230 status = qmi_wwan_register_subdriver(dev);
251 status = usbnet_get_endpoints(dev, intf); 231 if (status < 0) {
232 usb_set_intfdata(info->data, NULL);
233 usb_driver_release_interface(driver, info->data);
234 }
252 235
253err: 236err:
254 return status; 237 return status;
@@ -257,11 +240,7 @@ err:
257/* Some devices combine the "control" and "data" functions into a 240/* Some devices combine the "control" and "data" functions into a
258 * single interface with all three endpoints: interrupt + bulk in and 241 * single interface with all three endpoints: interrupt + bulk in and
259 * out 242 * out
260 * 243 */
261 * Setting up cdc-wdm as a subdriver owning the interrupt endpoint
262 * will let it provide userspace access to the encapsulated QMI
263 * protocol without interfering with the usbnet operations.
264 */
265static int qmi_wwan_bind_shared(struct usbnet *dev, struct usb_interface *intf) 244static int qmi_wwan_bind_shared(struct usbnet *dev, struct usb_interface *intf)
266{ 245{
267 int rv; 246 int rv;
@@ -313,14 +292,30 @@ err:
313 return rv; 292 return rv;
314} 293}
315 294
316static void qmi_wwan_unbind_shared(struct usbnet *dev, struct usb_interface *intf) 295static void qmi_wwan_unbind(struct usbnet *dev, struct usb_interface *intf)
317{ 296{
318 struct qmi_wwan_state *info = (void *)&dev->data; 297 struct qmi_wwan_state *info = (void *)&dev->data;
298 struct usb_driver *driver = driver_of(intf);
299 struct usb_interface *other;
319 300
320 if (info->subdriver && info->subdriver->disconnect) 301 if (info->subdriver && info->subdriver->disconnect)
321 info->subdriver->disconnect(intf); 302 info->subdriver->disconnect(info->control);
303
304 /* allow user to unbind using either control or data */
305 if (intf == info->control)
306 other = info->data;
307 else
308 other = info->control;
309
310 /* only if not shared */
311 if (other && intf != other) {
312 usb_set_intfdata(other, NULL);
313 usb_driver_release_interface(driver, other);
314 }
322 315
323 info->subdriver = NULL; 316 info->subdriver = NULL;
317 info->data = NULL;
318 info->control = NULL;
324} 319}
325 320
326/* suspend/resume wrappers calling both usbnet and the cdc-wdm 321/* suspend/resume wrappers calling both usbnet and the cdc-wdm
@@ -364,11 +359,11 @@ err:
364 return ret; 359 return ret;
365} 360}
366 361
367
368static const struct driver_info qmi_wwan_info = { 362static const struct driver_info qmi_wwan_info = {
369 .description = "QMI speaking wwan device", 363 .description = "QMI speaking wwan device",
370 .flags = FLAG_WWAN, 364 .flags = FLAG_WWAN,
371 .bind = qmi_wwan_bind, 365 .bind = qmi_wwan_bind,
366 .unbind = qmi_wwan_unbind,
372 .manage_power = qmi_wwan_manage_power, 367 .manage_power = qmi_wwan_manage_power,
373}; 368};
374 369
@@ -376,7 +371,7 @@ static const struct driver_info qmi_wwan_shared = {
376 .description = "QMI speaking wwan device with combined interface", 371 .description = "QMI speaking wwan device with combined interface",
377 .flags = FLAG_WWAN, 372 .flags = FLAG_WWAN,
378 .bind = qmi_wwan_bind_shared, 373 .bind = qmi_wwan_bind_shared,
379 .unbind = qmi_wwan_unbind_shared, 374 .unbind = qmi_wwan_unbind,
380 .manage_power = qmi_wwan_manage_power, 375 .manage_power = qmi_wwan_manage_power,
381}; 376};
382 377
@@ -384,7 +379,7 @@ static const struct driver_info qmi_wwan_gobi = {
384 .description = "Qualcomm Gobi wwan/QMI device", 379 .description = "Qualcomm Gobi wwan/QMI device",
385 .flags = FLAG_WWAN, 380 .flags = FLAG_WWAN,
386 .bind = qmi_wwan_bind_gobi, 381 .bind = qmi_wwan_bind_gobi,
387 .unbind = qmi_wwan_unbind_shared, 382 .unbind = qmi_wwan_unbind,
388 .manage_power = qmi_wwan_manage_power, 383 .manage_power = qmi_wwan_manage_power,
389}; 384};
390 385
@@ -393,7 +388,7 @@ static const struct driver_info qmi_wwan_force_int1 = {
393 .description = "Qualcomm WWAN/QMI device", 388 .description = "Qualcomm WWAN/QMI device",
394 .flags = FLAG_WWAN, 389 .flags = FLAG_WWAN,
395 .bind = qmi_wwan_bind_shared, 390 .bind = qmi_wwan_bind_shared,
396 .unbind = qmi_wwan_unbind_shared, 391 .unbind = qmi_wwan_unbind,
397 .manage_power = qmi_wwan_manage_power, 392 .manage_power = qmi_wwan_manage_power,
398 .data = BIT(1), /* interface whitelist bitmap */ 393 .data = BIT(1), /* interface whitelist bitmap */
399}; 394};
@@ -402,7 +397,7 @@ static const struct driver_info qmi_wwan_force_int4 = {
402 .description = "Qualcomm WWAN/QMI device", 397 .description = "Qualcomm WWAN/QMI device",
403 .flags = FLAG_WWAN, 398 .flags = FLAG_WWAN,
404 .bind = qmi_wwan_bind_shared, 399 .bind = qmi_wwan_bind_shared,
405 .unbind = qmi_wwan_unbind_shared, 400 .unbind = qmi_wwan_unbind,
406 .manage_power = qmi_wwan_manage_power, 401 .manage_power = qmi_wwan_manage_power,
407 .data = BIT(4), /* interface whitelist bitmap */ 402 .data = BIT(4), /* interface whitelist bitmap */
408}; 403};
@@ -424,7 +419,7 @@ static const struct driver_info qmi_wwan_sierra = {
424 .description = "Sierra Wireless wwan/QMI device", 419 .description = "Sierra Wireless wwan/QMI device",
425 .flags = FLAG_WWAN, 420 .flags = FLAG_WWAN,
426 .bind = qmi_wwan_bind_gobi, 421 .bind = qmi_wwan_bind_gobi,
427 .unbind = qmi_wwan_unbind_shared, 422 .unbind = qmi_wwan_unbind,
428 .manage_power = qmi_wwan_manage_power, 423 .manage_power = qmi_wwan_manage_power,
429 .data = BIT(8) | BIT(19), /* interface whitelist bitmap */ 424 .data = BIT(8) | BIT(19), /* interface whitelist bitmap */
430}; 425};
@@ -440,7 +435,7 @@ static const struct usb_device_id products[] = {
440 .idVendor = HUAWEI_VENDOR_ID, 435 .idVendor = HUAWEI_VENDOR_ID,
441 .bInterfaceClass = USB_CLASS_VENDOR_SPEC, 436 .bInterfaceClass = USB_CLASS_VENDOR_SPEC,
442 .bInterfaceSubClass = 1, 437 .bInterfaceSubClass = 1,
443 .bInterfaceProtocol = 8, /* NOTE: This is the *slave* interface of the CDC Union! */ 438 .bInterfaceProtocol = 9, /* CDC Ethernet *control* interface */
444 .driver_info = (unsigned long)&qmi_wwan_info, 439 .driver_info = (unsigned long)&qmi_wwan_info,
445 }, 440 },
446 { /* Vodafone/Huawei K5005 (12d1:14c8) and similar modems */ 441 { /* Vodafone/Huawei K5005 (12d1:14c8) and similar modems */
@@ -448,7 +443,7 @@ static const struct usb_device_id products[] = {
448 .idVendor = HUAWEI_VENDOR_ID, 443 .idVendor = HUAWEI_VENDOR_ID,
449 .bInterfaceClass = USB_CLASS_VENDOR_SPEC, 444 .bInterfaceClass = USB_CLASS_VENDOR_SPEC,
450 .bInterfaceSubClass = 1, 445 .bInterfaceSubClass = 1,
451 .bInterfaceProtocol = 56, /* NOTE: This is the *slave* interface of the CDC Union! */ 446 .bInterfaceProtocol = 57, /* CDC Ethernet *control* interface */
452 .driver_info = (unsigned long)&qmi_wwan_info, 447 .driver_info = (unsigned long)&qmi_wwan_info,
453 }, 448 },
454 { /* Huawei E392, E398 and possibly others in "Windows mode" 449 { /* Huawei E392, E398 and possibly others in "Windows mode"