linux/drivers/net/usb/huawei_cdc_ncm.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/* huawei_cdc_ncm.c - handles Huawei devices using the CDC NCM protocol as
   3 * transport layer.
   4 * Copyright (C) 2013    Enrico Mioso <mrkiko.rs@gmail.com>
   5 *
   6 * ABSTRACT:
   7 * This driver handles devices resembling the CDC NCM standard, but
   8 * encapsulating another protocol inside it. An example are some Huawei 3G
   9 * devices, exposing an embedded AT channel where you can set up the NCM
  10 * connection.
  11 * This code has been heavily inspired by the cdc_mbim.c driver, which is
  12 * Copyright (c) 2012  Smith Micro Software, Inc.
  13 * Copyright (c) 2012  Bjørn Mork <bjorn@mork.no>
  14 */
  15
  16#include <linux/module.h>
  17#include <linux/netdevice.h>
  18#include <linux/ethtool.h>
  19#include <linux/if_vlan.h>
  20#include <linux/ip.h>
  21#include <linux/mii.h>
  22#include <linux/usb.h>
  23#include <linux/usb/cdc.h>
  24#include <linux/usb/usbnet.h>
  25#include <linux/usb/cdc-wdm.h>
  26#include <linux/usb/cdc_ncm.h>
  27
  28/* Driver data */
  29struct huawei_cdc_ncm_state {
  30        struct cdc_ncm_ctx *ctx;
  31        atomic_t pmcount;
  32        struct usb_driver *subdriver;
  33        struct usb_interface *control;
  34        struct usb_interface *data;
  35};
  36
  37static int huawei_cdc_ncm_manage_power(struct usbnet *usbnet_dev, int on)
  38{
  39        struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data;
  40        int rv;
  41
  42        if ((on && atomic_add_return(1, &drvstate->pmcount) == 1) ||
  43                        (!on && atomic_dec_and_test(&drvstate->pmcount))) {
  44                rv = usb_autopm_get_interface(usbnet_dev->intf);
  45                usbnet_dev->intf->needs_remote_wakeup = on;
  46                if (!rv)
  47                        usb_autopm_put_interface(usbnet_dev->intf);
  48        }
  49        return 0;
  50}
  51
  52static int huawei_cdc_ncm_wdm_manage_power(struct usb_interface *intf,
  53                                           int status)
  54{
  55        struct usbnet *usbnet_dev = usb_get_intfdata(intf);
  56
  57        /* can be called while disconnecting */
  58        if (!usbnet_dev)
  59                return 0;
  60
  61        return huawei_cdc_ncm_manage_power(usbnet_dev, status);
  62}
  63
  64
  65static int huawei_cdc_ncm_bind(struct usbnet *usbnet_dev,
  66                               struct usb_interface *intf)
  67{
  68        struct cdc_ncm_ctx *ctx;
  69        struct usb_driver *subdriver = ERR_PTR(-ENODEV);
  70        int ret;
  71        struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data;
  72        int drvflags = 0;
  73
  74        /* altsetting should always be 1 for NCM devices - so we hard-coded
  75         * it here. Some huawei devices will need the NDP part of the NCM package to
  76         * be at the end of the frame.
  77         */
  78        drvflags |= CDC_NCM_FLAG_NDP_TO_END;
  79
  80        /* For many Huawei devices the NTB32 mode is the default and the best mode
  81         * they work with. Huawei E5785 and E5885 devices refuse to work in NTB16 mode at all.
  82         */
  83        drvflags |= CDC_NCM_FLAG_PREFER_NTB32;
  84
  85        ret = cdc_ncm_bind_common(usbnet_dev, intf, 1, drvflags);
  86        if (ret)
  87                goto err;
  88
  89        ctx = drvstate->ctx;
  90
  91        if (usbnet_dev->status)
  92                /* The wMaxCommand buffer must be big enough to hold
  93                 * any message from the modem. Experience has shown
  94                 * that some replies are more than 256 bytes long
  95                 */
  96                subdriver = usb_cdc_wdm_register(ctx->control,
  97                                                 &usbnet_dev->status->desc,
  98                                                 1024, /* wMaxCommand */
  99                                                 WWAN_PORT_AT,
 100                                                 huawei_cdc_ncm_wdm_manage_power);
 101        if (IS_ERR(subdriver)) {
 102                ret = PTR_ERR(subdriver);
 103                cdc_ncm_unbind(usbnet_dev, intf);
 104                goto err;
 105        }
 106
 107        /* Prevent usbnet from using the status descriptor */
 108        usbnet_dev->status = NULL;
 109
 110        drvstate->subdriver = subdriver;
 111
 112err:
 113        return ret;
 114}
 115
 116static void huawei_cdc_ncm_unbind(struct usbnet *usbnet_dev,
 117                                  struct usb_interface *intf)
 118{
 119        struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data;
 120        struct cdc_ncm_ctx *ctx = drvstate->ctx;
 121
 122        if (drvstate->subdriver && drvstate->subdriver->disconnect)
 123                drvstate->subdriver->disconnect(ctx->control);
 124        drvstate->subdriver = NULL;
 125
 126        cdc_ncm_unbind(usbnet_dev, intf);
 127}
 128
 129static int huawei_cdc_ncm_suspend(struct usb_interface *intf,
 130                                  pm_message_t message)
 131{
 132        int ret = 0;
 133        struct usbnet *usbnet_dev = usb_get_intfdata(intf);
 134        struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data;
 135        struct cdc_ncm_ctx *ctx = drvstate->ctx;
 136
 137        if (ctx == NULL) {
 138                ret = -ENODEV;
 139                goto error;
 140        }
 141
 142        ret = usbnet_suspend(intf, message);
 143        if (ret < 0)
 144                goto error;
 145
 146        if (intf == ctx->control &&
 147                drvstate->subdriver &&
 148                drvstate->subdriver->suspend)
 149                ret = drvstate->subdriver->suspend(intf, message);
 150        if (ret < 0)
 151                usbnet_resume(intf);
 152
 153error:
 154        return ret;
 155}
 156
 157static int huawei_cdc_ncm_resume(struct usb_interface *intf)
 158{
 159        int ret = 0;
 160        struct usbnet *usbnet_dev = usb_get_intfdata(intf);
 161        struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data;
 162        bool callsub;
 163        struct cdc_ncm_ctx *ctx = drvstate->ctx;
 164
 165        /* should we call subdriver's resume function? */
 166        callsub =
 167                (intf == ctx->control &&
 168                drvstate->subdriver &&
 169                drvstate->subdriver->resume);
 170
 171        if (callsub)
 172                ret = drvstate->subdriver->resume(intf);
 173        if (ret < 0)
 174                goto err;
 175        ret = usbnet_resume(intf);
 176        if (ret < 0 && callsub)
 177                drvstate->subdriver->suspend(intf, PMSG_SUSPEND);
 178err:
 179        return ret;
 180}
 181
 182static const struct driver_info huawei_cdc_ncm_info = {
 183        .description = "Huawei CDC NCM device",
 184        .flags = FLAG_NO_SETINT | FLAG_MULTI_PACKET | FLAG_WWAN,
 185        .bind = huawei_cdc_ncm_bind,
 186        .unbind = huawei_cdc_ncm_unbind,
 187        .manage_power = huawei_cdc_ncm_manage_power,
 188        .rx_fixup = cdc_ncm_rx_fixup,
 189        .tx_fixup = cdc_ncm_tx_fixup,
 190};
 191
 192static const struct usb_device_id huawei_cdc_ncm_devs[] = {
 193        /* Huawei NCM devices disguised as vendor specific */
 194        { USB_VENDOR_AND_INTERFACE_INFO(0x12d1, 0xff, 0x02, 0x16),
 195          .driver_info = (unsigned long)&huawei_cdc_ncm_info,
 196        },
 197        { USB_VENDOR_AND_INTERFACE_INFO(0x12d1, 0xff, 0x02, 0x46),
 198          .driver_info = (unsigned long)&huawei_cdc_ncm_info,
 199        },
 200        { USB_VENDOR_AND_INTERFACE_INFO(0x12d1, 0xff, 0x02, 0x76),
 201          .driver_info = (unsigned long)&huawei_cdc_ncm_info,
 202        },
 203        { USB_VENDOR_AND_INTERFACE_INFO(0x12d1, 0xff, 0x03, 0x16),
 204          .driver_info = (unsigned long)&huawei_cdc_ncm_info,
 205        },
 206
 207        /* Terminating entry */
 208        {
 209        },
 210};
 211MODULE_DEVICE_TABLE(usb, huawei_cdc_ncm_devs);
 212
 213static struct usb_driver huawei_cdc_ncm_driver = {
 214        .name = "huawei_cdc_ncm",
 215        .id_table = huawei_cdc_ncm_devs,
 216        .probe = usbnet_probe,
 217        .disconnect = usbnet_disconnect,
 218        .suspend = huawei_cdc_ncm_suspend,
 219        .resume = huawei_cdc_ncm_resume,
 220        .reset_resume = huawei_cdc_ncm_resume,
 221        .supports_autosuspend = 1,
 222        .disable_hub_initiated_lpm = 1,
 223};
 224module_usb_driver(huawei_cdc_ncm_driver);
 225MODULE_AUTHOR("Enrico Mioso <mrkiko.rs@gmail.com>");
 226MODULE_DESCRIPTION("USB CDC NCM host driver with encapsulated protocol support");
 227MODULE_LICENSE("GPL");
 228