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 = -ENODEV;
  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        /* Additionally, it has been reported that some Huawei E3372H devices, with
  81         * firmware version 21.318.01.00.541, come out of reset in NTB32 format mode, hence
  82         * needing to be set to the NTB16 one again.
  83         */
  84        drvflags |= CDC_NCM_FLAG_RESET_NTB16;
  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                                                 huawei_cdc_ncm_wdm_manage_power);
 100        if (IS_ERR(subdriver)) {
 101                ret = PTR_ERR(subdriver);
 102                cdc_ncm_unbind(usbnet_dev, intf);
 103                goto err;
 104        }
 105
 106        /* Prevent usbnet from using the status descriptor */
 107        usbnet_dev->status = NULL;
 108
 109        drvstate->subdriver = subdriver;
 110
 111err:
 112        return ret;
 113}
 114
 115static void huawei_cdc_ncm_unbind(struct usbnet *usbnet_dev,
 116                                  struct usb_interface *intf)
 117{
 118        struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data;
 119        struct cdc_ncm_ctx *ctx = drvstate->ctx;
 120
 121        if (drvstate->subdriver && drvstate->subdriver->disconnect)
 122                drvstate->subdriver->disconnect(ctx->control);
 123        drvstate->subdriver = NULL;
 124
 125        cdc_ncm_unbind(usbnet_dev, intf);
 126}
 127
 128static int huawei_cdc_ncm_suspend(struct usb_interface *intf,
 129                                  pm_message_t message)
 130{
 131        int ret = 0;
 132        struct usbnet *usbnet_dev = usb_get_intfdata(intf);
 133        struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data;
 134        struct cdc_ncm_ctx *ctx = drvstate->ctx;
 135
 136        if (ctx == NULL) {
 137                ret = -ENODEV;
 138                goto error;
 139        }
 140
 141        ret = usbnet_suspend(intf, message);
 142        if (ret < 0)
 143                goto error;
 144
 145        if (intf == ctx->control &&
 146                drvstate->subdriver &&
 147                drvstate->subdriver->suspend)
 148                ret = drvstate->subdriver->suspend(intf, message);
 149        if (ret < 0)
 150                usbnet_resume(intf);
 151
 152error:
 153        return ret;
 154}
 155
 156static int huawei_cdc_ncm_resume(struct usb_interface *intf)
 157{
 158        int ret = 0;
 159        struct usbnet *usbnet_dev = usb_get_intfdata(intf);
 160        struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data;
 161        bool callsub;
 162        struct cdc_ncm_ctx *ctx = drvstate->ctx;
 163
 164        /* should we call subdriver's resume function? */
 165        callsub =
 166                (intf == ctx->control &&
 167                drvstate->subdriver &&
 168                drvstate->subdriver->resume);
 169
 170        if (callsub)
 171                ret = drvstate->subdriver->resume(intf);
 172        if (ret < 0)
 173                goto err;
 174        ret = usbnet_resume(intf);
 175        if (ret < 0 && callsub)
 176                drvstate->subdriver->suspend(intf, PMSG_SUSPEND);
 177err:
 178        return ret;
 179}
 180
 181static const struct driver_info huawei_cdc_ncm_info = {
 182        .description = "Huawei CDC NCM device",
 183        .flags = FLAG_NO_SETINT | FLAG_MULTI_PACKET | FLAG_WWAN,
 184        .bind = huawei_cdc_ncm_bind,
 185        .unbind = huawei_cdc_ncm_unbind,
 186        .manage_power = huawei_cdc_ncm_manage_power,
 187        .rx_fixup = cdc_ncm_rx_fixup,
 188        .tx_fixup = cdc_ncm_tx_fixup,
 189};
 190
 191static const struct usb_device_id huawei_cdc_ncm_devs[] = {
 192        /* Huawei NCM devices disguised as vendor specific */
 193        { USB_VENDOR_AND_INTERFACE_INFO(0x12d1, 0xff, 0x02, 0x16),
 194          .driver_info = (unsigned long)&huawei_cdc_ncm_info,
 195        },
 196        { USB_VENDOR_AND_INTERFACE_INFO(0x12d1, 0xff, 0x02, 0x46),
 197          .driver_info = (unsigned long)&huawei_cdc_ncm_info,
 198        },
 199        { USB_VENDOR_AND_INTERFACE_INFO(0x12d1, 0xff, 0x02, 0x76),
 200          .driver_info = (unsigned long)&huawei_cdc_ncm_info,
 201        },
 202        { USB_VENDOR_AND_INTERFACE_INFO(0x12d1, 0xff, 0x03, 0x16),
 203          .driver_info = (unsigned long)&huawei_cdc_ncm_info,
 204        },
 205
 206        /* Terminating entry */
 207        {
 208        },
 209};
 210MODULE_DEVICE_TABLE(usb, huawei_cdc_ncm_devs);
 211
 212static struct usb_driver huawei_cdc_ncm_driver = {
 213        .name = "huawei_cdc_ncm",
 214        .id_table = huawei_cdc_ncm_devs,
 215        .probe = usbnet_probe,
 216        .disconnect = usbnet_disconnect,
 217        .suspend = huawei_cdc_ncm_suspend,
 218        .resume = huawei_cdc_ncm_resume,
 219        .reset_resume = huawei_cdc_ncm_resume,
 220        .supports_autosuspend = 1,
 221        .disable_hub_initiated_lpm = 1,
 222};
 223module_usb_driver(huawei_cdc_ncm_driver);
 224MODULE_AUTHOR("Enrico Mioso <mrkiko.rs@gmail.com>");
 225MODULE_DESCRIPTION("USB CDC NCM host driver with encapsulated protocol support");
 226MODULE_LICENSE("GPL");
 227