|  | /* huawei_cdc_ncm.c - handles Huawei devices using the CDC NCM protocol as | 
|  | * transport layer. | 
|  | * Copyright (C) 2013	 Enrico Mioso <mrkiko.rs@gmail.com> | 
|  | * | 
|  | * | 
|  | * ABSTRACT: | 
|  | * This driver handles devices resembling the CDC NCM standard, but | 
|  | * encapsulating another protocol inside it. An example are some Huawei 3G | 
|  | * devices, exposing an embedded AT channel where you can set up the NCM | 
|  | * connection. | 
|  | * This code has been heavily inspired by the cdc_mbim.c driver, which is | 
|  | * Copyright (c) 2012  Smith Micro Software, Inc. | 
|  | * Copyright (c) 2012  Bjørn Mork <bjorn@mork.no> | 
|  | * | 
|  | * This program is free software; you can redistribute it and/or | 
|  | * modify it under the terms of the GNU General Public License | 
|  | * version 2 as published by the Free Software Foundation. | 
|  | */ | 
|  |  | 
|  | #include <linux/module.h> | 
|  | #include <linux/netdevice.h> | 
|  | #include <linux/ethtool.h> | 
|  | #include <linux/if_vlan.h> | 
|  | #include <linux/ip.h> | 
|  | #include <linux/mii.h> | 
|  | #include <linux/usb.h> | 
|  | #include <linux/usb/cdc.h> | 
|  | #include <linux/usb/usbnet.h> | 
|  | #include <linux/usb/cdc-wdm.h> | 
|  | #include <linux/usb/cdc_ncm.h> | 
|  |  | 
|  | /* Driver data */ | 
|  | struct huawei_cdc_ncm_state { | 
|  | struct cdc_ncm_ctx *ctx; | 
|  | atomic_t pmcount; | 
|  | struct usb_driver *subdriver; | 
|  | struct usb_interface *control; | 
|  | struct usb_interface *data; | 
|  | }; | 
|  |  | 
|  | static int huawei_cdc_ncm_manage_power(struct usbnet *usbnet_dev, int on) | 
|  | { | 
|  | struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data; | 
|  | int rv; | 
|  |  | 
|  | if ((on && atomic_add_return(1, &drvstate->pmcount) == 1) || | 
|  | (!on && atomic_dec_and_test(&drvstate->pmcount))) { | 
|  | rv = usb_autopm_get_interface(usbnet_dev->intf); | 
|  | usbnet_dev->intf->needs_remote_wakeup = on; | 
|  | if (!rv) | 
|  | usb_autopm_put_interface(usbnet_dev->intf); | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int huawei_cdc_ncm_wdm_manage_power(struct usb_interface *intf, | 
|  | int status) | 
|  | { | 
|  | struct usbnet *usbnet_dev = usb_get_intfdata(intf); | 
|  |  | 
|  | /* can be called while disconnecting */ | 
|  | if (!usbnet_dev) | 
|  | return 0; | 
|  |  | 
|  | return huawei_cdc_ncm_manage_power(usbnet_dev, status); | 
|  | } | 
|  |  | 
|  |  | 
|  | static int huawei_cdc_ncm_bind(struct usbnet *usbnet_dev, | 
|  | struct usb_interface *intf) | 
|  | { | 
|  | struct cdc_ncm_ctx *ctx; | 
|  | struct usb_driver *subdriver = ERR_PTR(-ENODEV); | 
|  | int ret = -ENODEV; | 
|  | struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data; | 
|  |  | 
|  | /* altsetting should always be 1 for NCM devices - so we hard-coded | 
|  | * it here | 
|  | */ | 
|  | ret = cdc_ncm_bind_common(usbnet_dev, intf, 1); | 
|  | if (ret) | 
|  | goto err; | 
|  |  | 
|  | ctx = drvstate->ctx; | 
|  |  | 
|  | if (usbnet_dev->status) | 
|  | /* The wMaxCommand buffer must be big enough to hold | 
|  | * any message from the modem. Experience has shown | 
|  | * that some replies are more than 256 bytes long | 
|  | */ | 
|  | subdriver = usb_cdc_wdm_register(ctx->control, | 
|  | &usbnet_dev->status->desc, | 
|  | 1024, /* wMaxCommand */ | 
|  | huawei_cdc_ncm_wdm_manage_power); | 
|  | if (IS_ERR(subdriver)) { | 
|  | ret = PTR_ERR(subdriver); | 
|  | cdc_ncm_unbind(usbnet_dev, intf); | 
|  | goto err; | 
|  | } | 
|  |  | 
|  | /* Prevent usbnet from using the status descriptor */ | 
|  | usbnet_dev->status = NULL; | 
|  |  | 
|  | drvstate->subdriver = subdriver; | 
|  |  | 
|  | err: | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static void huawei_cdc_ncm_unbind(struct usbnet *usbnet_dev, | 
|  | struct usb_interface *intf) | 
|  | { | 
|  | struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data; | 
|  | struct cdc_ncm_ctx *ctx = drvstate->ctx; | 
|  |  | 
|  | if (drvstate->subdriver && drvstate->subdriver->disconnect) | 
|  | drvstate->subdriver->disconnect(ctx->control); | 
|  | drvstate->subdriver = NULL; | 
|  |  | 
|  | cdc_ncm_unbind(usbnet_dev, intf); | 
|  | } | 
|  |  | 
|  | static int huawei_cdc_ncm_suspend(struct usb_interface *intf, | 
|  | pm_message_t message) | 
|  | { | 
|  | int ret = 0; | 
|  | struct usbnet *usbnet_dev = usb_get_intfdata(intf); | 
|  | struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data; | 
|  | struct cdc_ncm_ctx *ctx = drvstate->ctx; | 
|  |  | 
|  | if (ctx == NULL) { | 
|  | ret = -ENODEV; | 
|  | goto error; | 
|  | } | 
|  |  | 
|  | ret = usbnet_suspend(intf, message); | 
|  | if (ret < 0) | 
|  | goto error; | 
|  |  | 
|  | if (intf == ctx->control && | 
|  | drvstate->subdriver && | 
|  | drvstate->subdriver->suspend) | 
|  | ret = drvstate->subdriver->suspend(intf, message); | 
|  | if (ret < 0) | 
|  | usbnet_resume(intf); | 
|  |  | 
|  | error: | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int huawei_cdc_ncm_resume(struct usb_interface *intf) | 
|  | { | 
|  | int ret = 0; | 
|  | struct usbnet *usbnet_dev = usb_get_intfdata(intf); | 
|  | struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data; | 
|  | bool callsub; | 
|  | struct cdc_ncm_ctx *ctx = drvstate->ctx; | 
|  |  | 
|  | /* should we call subdriver's resume function? */ | 
|  | callsub = | 
|  | (intf == ctx->control && | 
|  | drvstate->subdriver && | 
|  | drvstate->subdriver->resume); | 
|  |  | 
|  | if (callsub) | 
|  | ret = drvstate->subdriver->resume(intf); | 
|  | if (ret < 0) | 
|  | goto err; | 
|  | ret = usbnet_resume(intf); | 
|  | if (ret < 0 && callsub) | 
|  | drvstate->subdriver->suspend(intf, PMSG_SUSPEND); | 
|  | err: | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static const struct driver_info huawei_cdc_ncm_info = { | 
|  | .description = "Huawei CDC NCM device", | 
|  | .flags = FLAG_NO_SETINT | FLAG_MULTI_PACKET | FLAG_WWAN, | 
|  | .bind = huawei_cdc_ncm_bind, | 
|  | .unbind = huawei_cdc_ncm_unbind, | 
|  | .manage_power = huawei_cdc_ncm_manage_power, | 
|  | .rx_fixup = cdc_ncm_rx_fixup, | 
|  | .tx_fixup = cdc_ncm_tx_fixup, | 
|  | }; | 
|  |  | 
|  | static const struct usb_device_id huawei_cdc_ncm_devs[] = { | 
|  | /* Huawei NCM devices disguised as vendor specific */ | 
|  | { USB_VENDOR_AND_INTERFACE_INFO(0x12d1, 0xff, 0x02, 0x16), | 
|  | .driver_info = (unsigned long)&huawei_cdc_ncm_info, | 
|  | }, | 
|  | { USB_VENDOR_AND_INTERFACE_INFO(0x12d1, 0xff, 0x02, 0x46), | 
|  | .driver_info = (unsigned long)&huawei_cdc_ncm_info, | 
|  | }, | 
|  | { USB_VENDOR_AND_INTERFACE_INFO(0x12d1, 0xff, 0x02, 0x76), | 
|  | .driver_info = (unsigned long)&huawei_cdc_ncm_info, | 
|  | }, | 
|  | { USB_VENDOR_AND_INTERFACE_INFO(0x12d1, 0xff, 0x03, 0x16), | 
|  | .driver_info = (unsigned long)&huawei_cdc_ncm_info, | 
|  | }, | 
|  |  | 
|  | /* Terminating entry */ | 
|  | { | 
|  | }, | 
|  | }; | 
|  | MODULE_DEVICE_TABLE(usb, huawei_cdc_ncm_devs); | 
|  |  | 
|  | static struct usb_driver huawei_cdc_ncm_driver = { | 
|  | .name = "huawei_cdc_ncm", | 
|  | .id_table = huawei_cdc_ncm_devs, | 
|  | .probe = usbnet_probe, | 
|  | .disconnect = usbnet_disconnect, | 
|  | .suspend = huawei_cdc_ncm_suspend, | 
|  | .resume = huawei_cdc_ncm_resume, | 
|  | .reset_resume = huawei_cdc_ncm_resume, | 
|  | .supports_autosuspend = 1, | 
|  | .disable_hub_initiated_lpm = 1, | 
|  | }; | 
|  | module_usb_driver(huawei_cdc_ncm_driver); | 
|  | MODULE_AUTHOR("Enrico Mioso <mrkiko.rs@gmail.com>"); | 
|  | MODULE_DESCRIPTION("USB CDC NCM host driver with encapsulated protocol support"); | 
|  | MODULE_LICENSE("GPL"); |