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