Commit e4bf6348 authored by Kristian Evensen's avatar Kristian Evensen Committed by David S. Miller

qmi_wwan: Add quirk for Quectel dynamic config

Most, if not all, Quectel devices use dynamic interface numbers, and
users are able to change the USB configuration at will. Matching on for
example interface number is therefore not possible.

Instead, the QMI device can be identified by looking at the interface
class, subclass and protocol (all 0xff), as well as the number of
endpoints. The reason we need to look at the number of endpoints, is
that the diagnostic port interface has the same class, subclass and
protocol as QMI. However, the diagnostic port only has two endpoints,
while QMI has three.

Until now, we have identified the QMI device by combining a match on
class, subclass and protocol, with a call to the function
quectel_diag_detect(). In quectel_diag_detect(), we check if the number
of endpoints matches for known Quectel vendor/product ids.

Adding new vendor/product ids to quectel_diag_detect() is not a good
long-term solution. This commit replaces the function with a quirk, and
applies the quirk to affected Quectel devices that I have been able to
test the change with (EP06, EM12 and EC25). If the quirk is set and the
number of endpoints equal two, we return from qmi_wwan_probe() with
-ENODEV.
Signed-off-by: default avatarKristian Evensen <kristian.evensen@gmail.com>
Acked-by: default avatarBjørn Mork <bjorn@mork.no>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 22b56e82
...@@ -63,6 +63,7 @@ enum qmi_wwan_flags { ...@@ -63,6 +63,7 @@ enum qmi_wwan_flags {
enum qmi_wwan_quirks { enum qmi_wwan_quirks {
QMI_WWAN_QUIRK_DTR = 1 << 0, /* needs "set DTR" request */ QMI_WWAN_QUIRK_DTR = 1 << 0, /* needs "set DTR" request */
QMI_WWAN_QUIRK_QUECTEL_DYNCFG = 1 << 1, /* check num. endpoints */
}; };
struct qmimux_hdr { struct qmimux_hdr {
...@@ -845,6 +846,16 @@ static const struct driver_info qmi_wwan_info_quirk_dtr = { ...@@ -845,6 +846,16 @@ static const struct driver_info qmi_wwan_info_quirk_dtr = {
.data = QMI_WWAN_QUIRK_DTR, .data = QMI_WWAN_QUIRK_DTR,
}; };
static const struct driver_info qmi_wwan_info_quirk_quectel_dyncfg = {
.description = "WWAN/QMI device",
.flags = FLAG_WWAN | FLAG_SEND_ZLP,
.bind = qmi_wwan_bind,
.unbind = qmi_wwan_unbind,
.manage_power = qmi_wwan_manage_power,
.rx_fixup = qmi_wwan_rx_fixup,
.data = QMI_WWAN_QUIRK_DTR | QMI_WWAN_QUIRK_QUECTEL_DYNCFG,
};
#define HUAWEI_VENDOR_ID 0x12D1 #define HUAWEI_VENDOR_ID 0x12D1
/* map QMI/wwan function by a fixed interface number */ /* map QMI/wwan function by a fixed interface number */
...@@ -865,6 +876,15 @@ static const struct driver_info qmi_wwan_info_quirk_dtr = { ...@@ -865,6 +876,15 @@ static const struct driver_info qmi_wwan_info_quirk_dtr = {
#define QMI_GOBI_DEVICE(vend, prod) \ #define QMI_GOBI_DEVICE(vend, prod) \
QMI_FIXED_INTF(vend, prod, 0) QMI_FIXED_INTF(vend, prod, 0)
/* Quectel does not use fixed interface numbers on at least some of their
* devices. We need to check the number of endpoints to ensure that we bind to
* the correct interface.
*/
#define QMI_QUIRK_QUECTEL_DYNCFG(vend, prod) \
USB_DEVICE_AND_INTERFACE_INFO(vend, prod, USB_CLASS_VENDOR_SPEC, \
USB_SUBCLASS_VENDOR_SPEC, 0xff), \
.driver_info = (unsigned long)&qmi_wwan_info_quirk_quectel_dyncfg
static const struct usb_device_id products[] = { static const struct usb_device_id products[] = {
/* 1. CDC ECM like devices match on the control interface */ /* 1. CDC ECM like devices match on the control interface */
{ /* Huawei E392, E398 and possibly others sharing both device id and more... */ { /* Huawei E392, E398 and possibly others sharing both device id and more... */
...@@ -969,20 +989,9 @@ static const struct usb_device_id products[] = { ...@@ -969,20 +989,9 @@ static const struct usb_device_id products[] = {
USB_DEVICE_AND_INTERFACE_INFO(0x03f0, 0x581d, USB_CLASS_VENDOR_SPEC, 1, 7), USB_DEVICE_AND_INTERFACE_INFO(0x03f0, 0x581d, USB_CLASS_VENDOR_SPEC, 1, 7),
.driver_info = (unsigned long)&qmi_wwan_info, .driver_info = (unsigned long)&qmi_wwan_info,
}, },
{ /* Quectel EP06/EG06/EM06 */ {QMI_QUIRK_QUECTEL_DYNCFG(0x2c7c, 0x0125)}, /* Quectel EC25, EC20 R2.0 Mini PCIe */
USB_DEVICE_AND_INTERFACE_INFO(0x2c7c, 0x0306, {QMI_QUIRK_QUECTEL_DYNCFG(0x2c7c, 0x0306)}, /* Quectel EP06/EG06/EM06 */
USB_CLASS_VENDOR_SPEC, {QMI_QUIRK_QUECTEL_DYNCFG(0x2c7c, 0x0512)}, /* Quectel EG12/EM12 */
USB_SUBCLASS_VENDOR_SPEC,
0xff),
.driver_info = (unsigned long)&qmi_wwan_info_quirk_dtr,
},
{ /* Quectel EG12/EM12 */
USB_DEVICE_AND_INTERFACE_INFO(0x2c7c, 0x0512,
USB_CLASS_VENDOR_SPEC,
USB_SUBCLASS_VENDOR_SPEC,
0xff),
.driver_info = (unsigned long)&qmi_wwan_info_quirk_dtr,
},
/* 3. Combined interface devices matching on interface number */ /* 3. Combined interface devices matching on interface number */
{QMI_FIXED_INTF(0x0408, 0xea42, 4)}, /* Yota / Megafon M100-1 */ {QMI_FIXED_INTF(0x0408, 0xea42, 4)}, /* Yota / Megafon M100-1 */
...@@ -1271,7 +1280,6 @@ static const struct usb_device_id products[] = { ...@@ -1271,7 +1280,6 @@ static const struct usb_device_id products[] = {
{QMI_FIXED_INTF(0x03f0, 0x9d1d, 1)}, /* HP lt4120 Snapdragon X5 LTE */ {QMI_FIXED_INTF(0x03f0, 0x9d1d, 1)}, /* HP lt4120 Snapdragon X5 LTE */
{QMI_FIXED_INTF(0x22de, 0x9061, 3)}, /* WeTelecom WPD-600N */ {QMI_FIXED_INTF(0x22de, 0x9061, 3)}, /* WeTelecom WPD-600N */
{QMI_QUIRK_SET_DTR(0x1e0e, 0x9001, 5)}, /* SIMCom 7100E, 7230E, 7600E ++ */ {QMI_QUIRK_SET_DTR(0x1e0e, 0x9001, 5)}, /* SIMCom 7100E, 7230E, 7600E ++ */
{QMI_QUIRK_SET_DTR(0x2c7c, 0x0125, 4)}, /* Quectel EC25, EC20 R2.0 Mini PCIe */
{QMI_QUIRK_SET_DTR(0x2c7c, 0x0121, 4)}, /* Quectel EC21 Mini PCIe */ {QMI_QUIRK_SET_DTR(0x2c7c, 0x0121, 4)}, /* Quectel EC21 Mini PCIe */
{QMI_QUIRK_SET_DTR(0x2c7c, 0x0191, 4)}, /* Quectel EG91 */ {QMI_QUIRK_SET_DTR(0x2c7c, 0x0191, 4)}, /* Quectel EG91 */
{QMI_FIXED_INTF(0x2c7c, 0x0296, 4)}, /* Quectel BG96 */ {QMI_FIXED_INTF(0x2c7c, 0x0296, 4)}, /* Quectel BG96 */
...@@ -1351,27 +1359,12 @@ static bool quectel_ec20_detected(struct usb_interface *intf) ...@@ -1351,27 +1359,12 @@ static bool quectel_ec20_detected(struct usb_interface *intf)
return false; return false;
} }
static bool quectel_diag_detected(struct usb_interface *intf)
{
struct usb_device *dev = interface_to_usbdev(intf);
struct usb_interface_descriptor intf_desc = intf->cur_altsetting->desc;
u16 id_vendor = le16_to_cpu(dev->descriptor.idVendor);
u16 id_product = le16_to_cpu(dev->descriptor.idProduct);
if (id_vendor != 0x2c7c || intf_desc.bNumEndpoints != 2)
return false;
if (id_product == 0x0306 || id_product == 0x0512)
return true;
else
return false;
}
static int qmi_wwan_probe(struct usb_interface *intf, static int qmi_wwan_probe(struct usb_interface *intf,
const struct usb_device_id *prod) const struct usb_device_id *prod)
{ {
struct usb_device_id *id = (struct usb_device_id *)prod; struct usb_device_id *id = (struct usb_device_id *)prod;
struct usb_interface_descriptor *desc = &intf->cur_altsetting->desc; struct usb_interface_descriptor *desc = &intf->cur_altsetting->desc;
const struct driver_info *info;
/* Workaround to enable dynamic IDs. This disables usbnet /* Workaround to enable dynamic IDs. This disables usbnet
* blacklisting functionality. Which, if required, can be * blacklisting functionality. Which, if required, can be
...@@ -1405,10 +1398,14 @@ static int qmi_wwan_probe(struct usb_interface *intf, ...@@ -1405,10 +1398,14 @@ static int qmi_wwan_probe(struct usb_interface *intf,
* we need to match on class/subclass/protocol. These values are * we need to match on class/subclass/protocol. These values are
* identical for the diagnostic- and QMI-interface, but bNumEndpoints is * identical for the diagnostic- and QMI-interface, but bNumEndpoints is
* different. Ignore the current interface if the number of endpoints * different. Ignore the current interface if the number of endpoints
* the number for the diag interface (two). * equals the number for the diag interface (two).
*/ */
if (quectel_diag_detected(intf)) info = (void *)&id->driver_info;
return -ENODEV;
if (info->data & QMI_WWAN_QUIRK_QUECTEL_DYNCFG) {
if (desc->bNumEndpoints == 2)
return -ENODEV;
}
return usbnet_probe(intf, id); return usbnet_probe(intf, id);
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment