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