141c47d8cSEnrico Mioso /* huawei_cdc_ncm.c - handles Huawei devices using the CDC NCM protocol as 241c47d8cSEnrico Mioso * transport layer. 341c47d8cSEnrico Mioso * Copyright (C) 2013 Enrico Mioso <mrkiko.rs@gmail.com> 441c47d8cSEnrico Mioso * 541c47d8cSEnrico Mioso * 641c47d8cSEnrico Mioso * ABSTRACT: 741c47d8cSEnrico Mioso * This driver handles devices resembling the CDC NCM standard, but 841c47d8cSEnrico Mioso * encapsulating another protocol inside it. An example are some Huawei 3G 941c47d8cSEnrico Mioso * devices, exposing an embedded AT channel where you can set up the NCM 1041c47d8cSEnrico Mioso * connection. 1141c47d8cSEnrico Mioso * This code has been heavily inspired by the cdc_mbim.c driver, which is 1241c47d8cSEnrico Mioso * Copyright (c) 2012 Smith Micro Software, Inc. 1341c47d8cSEnrico Mioso * Copyright (c) 2012 Bjørn Mork <bjorn@mork.no> 1441c47d8cSEnrico Mioso * 1541c47d8cSEnrico Mioso * This program is free software; you can redistribute it and/or 1641c47d8cSEnrico Mioso * modify it under the terms of the GNU General Public License 1741c47d8cSEnrico Mioso * version 2 as published by the Free Software Foundation. 1841c47d8cSEnrico Mioso */ 1941c47d8cSEnrico Mioso 2041c47d8cSEnrico Mioso #include <linux/module.h> 2141c47d8cSEnrico Mioso #include <linux/netdevice.h> 2241c47d8cSEnrico Mioso #include <linux/ethtool.h> 2341c47d8cSEnrico Mioso #include <linux/if_vlan.h> 2441c47d8cSEnrico Mioso #include <linux/ip.h> 2541c47d8cSEnrico Mioso #include <linux/mii.h> 2641c47d8cSEnrico Mioso #include <linux/usb.h> 2741c47d8cSEnrico Mioso #include <linux/usb/cdc.h> 2841c47d8cSEnrico Mioso #include <linux/usb/usbnet.h> 2941c47d8cSEnrico Mioso #include <linux/usb/cdc-wdm.h> 3041c47d8cSEnrico Mioso #include <linux/usb/cdc_ncm.h> 3141c47d8cSEnrico Mioso 3241c47d8cSEnrico Mioso /* Driver data */ 3341c47d8cSEnrico Mioso struct huawei_cdc_ncm_state { 3441c47d8cSEnrico Mioso struct cdc_ncm_ctx *ctx; 3541c47d8cSEnrico Mioso atomic_t pmcount; 3641c47d8cSEnrico Mioso struct usb_driver *subdriver; 3741c47d8cSEnrico Mioso struct usb_interface *control; 3841c47d8cSEnrico Mioso struct usb_interface *data; 3941c47d8cSEnrico Mioso }; 4041c47d8cSEnrico Mioso 4141c47d8cSEnrico Mioso static int huawei_cdc_ncm_manage_power(struct usbnet *usbnet_dev, int on) 4241c47d8cSEnrico Mioso { 4341c47d8cSEnrico Mioso struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data; 4441c47d8cSEnrico Mioso int rv; 4541c47d8cSEnrico Mioso 4641c47d8cSEnrico Mioso if ((on && atomic_add_return(1, &drvstate->pmcount) == 1) || 4741c47d8cSEnrico Mioso (!on && atomic_dec_and_test(&drvstate->pmcount))) { 4841c47d8cSEnrico Mioso rv = usb_autopm_get_interface(usbnet_dev->intf); 4941c47d8cSEnrico Mioso usbnet_dev->intf->needs_remote_wakeup = on; 5041c47d8cSEnrico Mioso if (!rv) 5141c47d8cSEnrico Mioso usb_autopm_put_interface(usbnet_dev->intf); 5241c47d8cSEnrico Mioso } 5341c47d8cSEnrico Mioso return 0; 5441c47d8cSEnrico Mioso } 5541c47d8cSEnrico Mioso 5641c47d8cSEnrico Mioso static int huawei_cdc_ncm_wdm_manage_power(struct usb_interface *intf, 5741c47d8cSEnrico Mioso int status) 5841c47d8cSEnrico Mioso { 5941c47d8cSEnrico Mioso struct usbnet *usbnet_dev = usb_get_intfdata(intf); 6041c47d8cSEnrico Mioso 6141c47d8cSEnrico Mioso /* can be called while disconnecting */ 6241c47d8cSEnrico Mioso if (!usbnet_dev) 6341c47d8cSEnrico Mioso return 0; 6441c47d8cSEnrico Mioso 6541c47d8cSEnrico Mioso return huawei_cdc_ncm_manage_power(usbnet_dev, status); 6641c47d8cSEnrico Mioso } 6741c47d8cSEnrico Mioso 6841c47d8cSEnrico Mioso 6941c47d8cSEnrico Mioso static int huawei_cdc_ncm_bind(struct usbnet *usbnet_dev, 7041c47d8cSEnrico Mioso struct usb_interface *intf) 7141c47d8cSEnrico Mioso { 7241c47d8cSEnrico Mioso struct cdc_ncm_ctx *ctx; 7341c47d8cSEnrico Mioso struct usb_driver *subdriver = ERR_PTR(-ENODEV); 7441c47d8cSEnrico Mioso int ret = -ENODEV; 7541c47d8cSEnrico Mioso struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data; 7641c47d8cSEnrico Mioso 7741c47d8cSEnrico Mioso /* altsetting should always be 1 for NCM devices - so we hard-coded 7841c47d8cSEnrico Mioso * it here 7941c47d8cSEnrico Mioso */ 8041c47d8cSEnrico Mioso ret = cdc_ncm_bind_common(usbnet_dev, intf, 1); 8141c47d8cSEnrico Mioso if (ret) 8241c47d8cSEnrico Mioso goto err; 8341c47d8cSEnrico Mioso 8441c47d8cSEnrico Mioso ctx = drvstate->ctx; 8541c47d8cSEnrico Mioso 8641c47d8cSEnrico Mioso if (usbnet_dev->status) 87*3acc7461SBjørn Mork /* The wMaxCommand buffer must be big enough to hold 88*3acc7461SBjørn Mork * any message from the modem. Experience has shown 89*3acc7461SBjørn Mork * that some replies are more than 256 bytes long 9041c47d8cSEnrico Mioso */ 9141c47d8cSEnrico Mioso subdriver = usb_cdc_wdm_register(ctx->control, 9241c47d8cSEnrico Mioso &usbnet_dev->status->desc, 93*3acc7461SBjørn Mork 1024, /* wMaxCommand */ 9441c47d8cSEnrico Mioso huawei_cdc_ncm_wdm_manage_power); 9541c47d8cSEnrico Mioso if (IS_ERR(subdriver)) { 9641c47d8cSEnrico Mioso ret = PTR_ERR(subdriver); 9741c47d8cSEnrico Mioso cdc_ncm_unbind(usbnet_dev, intf); 9841c47d8cSEnrico Mioso goto err; 9941c47d8cSEnrico Mioso } 10041c47d8cSEnrico Mioso 10141c47d8cSEnrico Mioso /* Prevent usbnet from using the status descriptor */ 10241c47d8cSEnrico Mioso usbnet_dev->status = NULL; 10341c47d8cSEnrico Mioso 10441c47d8cSEnrico Mioso drvstate->subdriver = subdriver; 10541c47d8cSEnrico Mioso 10641c47d8cSEnrico Mioso err: 10741c47d8cSEnrico Mioso return ret; 10841c47d8cSEnrico Mioso } 10941c47d8cSEnrico Mioso 11041c47d8cSEnrico Mioso static void huawei_cdc_ncm_unbind(struct usbnet *usbnet_dev, 11141c47d8cSEnrico Mioso struct usb_interface *intf) 11241c47d8cSEnrico Mioso { 11341c47d8cSEnrico Mioso struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data; 11441c47d8cSEnrico Mioso struct cdc_ncm_ctx *ctx = drvstate->ctx; 11541c47d8cSEnrico Mioso 11641c47d8cSEnrico Mioso if (drvstate->subdriver && drvstate->subdriver->disconnect) 11741c47d8cSEnrico Mioso drvstate->subdriver->disconnect(ctx->control); 11841c47d8cSEnrico Mioso drvstate->subdriver = NULL; 11941c47d8cSEnrico Mioso 12041c47d8cSEnrico Mioso cdc_ncm_unbind(usbnet_dev, intf); 12141c47d8cSEnrico Mioso } 12241c47d8cSEnrico Mioso 12341c47d8cSEnrico Mioso static int huawei_cdc_ncm_suspend(struct usb_interface *intf, 12441c47d8cSEnrico Mioso pm_message_t message) 12541c47d8cSEnrico Mioso { 12641c47d8cSEnrico Mioso int ret = 0; 12741c47d8cSEnrico Mioso struct usbnet *usbnet_dev = usb_get_intfdata(intf); 12841c47d8cSEnrico Mioso struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data; 12941c47d8cSEnrico Mioso struct cdc_ncm_ctx *ctx = drvstate->ctx; 13041c47d8cSEnrico Mioso 13141c47d8cSEnrico Mioso if (ctx == NULL) { 13241c47d8cSEnrico Mioso ret = -ENODEV; 13341c47d8cSEnrico Mioso goto error; 13441c47d8cSEnrico Mioso } 13541c47d8cSEnrico Mioso 13641c47d8cSEnrico Mioso ret = usbnet_suspend(intf, message); 13741c47d8cSEnrico Mioso if (ret < 0) 13841c47d8cSEnrico Mioso goto error; 13941c47d8cSEnrico Mioso 14041c47d8cSEnrico Mioso if (intf == ctx->control && 14141c47d8cSEnrico Mioso drvstate->subdriver && 14241c47d8cSEnrico Mioso drvstate->subdriver->suspend) 14341c47d8cSEnrico Mioso ret = drvstate->subdriver->suspend(intf, message); 14441c47d8cSEnrico Mioso if (ret < 0) 14541c47d8cSEnrico Mioso usbnet_resume(intf); 14641c47d8cSEnrico Mioso 14741c47d8cSEnrico Mioso error: 14841c47d8cSEnrico Mioso return ret; 14941c47d8cSEnrico Mioso } 15041c47d8cSEnrico Mioso 15141c47d8cSEnrico Mioso static int huawei_cdc_ncm_resume(struct usb_interface *intf) 15241c47d8cSEnrico Mioso { 15341c47d8cSEnrico Mioso int ret = 0; 15441c47d8cSEnrico Mioso struct usbnet *usbnet_dev = usb_get_intfdata(intf); 15541c47d8cSEnrico Mioso struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data; 15641c47d8cSEnrico Mioso bool callsub; 15741c47d8cSEnrico Mioso struct cdc_ncm_ctx *ctx = drvstate->ctx; 15841c47d8cSEnrico Mioso 15941c47d8cSEnrico Mioso /* should we call subdriver's resume function? */ 16041c47d8cSEnrico Mioso callsub = 16141c47d8cSEnrico Mioso (intf == ctx->control && 16241c47d8cSEnrico Mioso drvstate->subdriver && 16341c47d8cSEnrico Mioso drvstate->subdriver->resume); 16441c47d8cSEnrico Mioso 16541c47d8cSEnrico Mioso if (callsub) 16641c47d8cSEnrico Mioso ret = drvstate->subdriver->resume(intf); 16741c47d8cSEnrico Mioso if (ret < 0) 16841c47d8cSEnrico Mioso goto err; 16941c47d8cSEnrico Mioso ret = usbnet_resume(intf); 17041c47d8cSEnrico Mioso if (ret < 0 && callsub) 17141c47d8cSEnrico Mioso drvstate->subdriver->suspend(intf, PMSG_SUSPEND); 17241c47d8cSEnrico Mioso err: 17341c47d8cSEnrico Mioso return ret; 17441c47d8cSEnrico Mioso } 17541c47d8cSEnrico Mioso 17641c47d8cSEnrico Mioso static const struct driver_info huawei_cdc_ncm_info = { 17741c47d8cSEnrico Mioso .description = "Huawei CDC NCM device", 17841c47d8cSEnrico Mioso .flags = FLAG_NO_SETINT | FLAG_MULTI_PACKET | FLAG_WWAN, 17941c47d8cSEnrico Mioso .bind = huawei_cdc_ncm_bind, 18041c47d8cSEnrico Mioso .unbind = huawei_cdc_ncm_unbind, 18141c47d8cSEnrico Mioso .manage_power = huawei_cdc_ncm_manage_power, 18241c47d8cSEnrico Mioso .rx_fixup = cdc_ncm_rx_fixup, 18341c47d8cSEnrico Mioso .tx_fixup = cdc_ncm_tx_fixup, 18441c47d8cSEnrico Mioso }; 18541c47d8cSEnrico Mioso 18641c47d8cSEnrico Mioso static const struct usb_device_id huawei_cdc_ncm_devs[] = { 18741c47d8cSEnrico Mioso /* Huawei NCM devices disguised as vendor specific */ 18841c47d8cSEnrico Mioso { USB_VENDOR_AND_INTERFACE_INFO(0x12d1, 0xff, 0x02, 0x16), 18941c47d8cSEnrico Mioso .driver_info = (unsigned long)&huawei_cdc_ncm_info, 19041c47d8cSEnrico Mioso }, 19141c47d8cSEnrico Mioso { USB_VENDOR_AND_INTERFACE_INFO(0x12d1, 0xff, 0x02, 0x46), 19241c47d8cSEnrico Mioso .driver_info = (unsigned long)&huawei_cdc_ncm_info, 19341c47d8cSEnrico Mioso }, 19441c47d8cSEnrico Mioso { USB_VENDOR_AND_INTERFACE_INFO(0x12d1, 0xff, 0x02, 0x76), 19541c47d8cSEnrico Mioso .driver_info = (unsigned long)&huawei_cdc_ncm_info, 19641c47d8cSEnrico Mioso }, 19741c47d8cSEnrico Mioso 19841c47d8cSEnrico Mioso /* Terminating entry */ 19941c47d8cSEnrico Mioso { 20041c47d8cSEnrico Mioso }, 20141c47d8cSEnrico Mioso }; 20241c47d8cSEnrico Mioso MODULE_DEVICE_TABLE(usb, huawei_cdc_ncm_devs); 20341c47d8cSEnrico Mioso 20441c47d8cSEnrico Mioso static struct usb_driver huawei_cdc_ncm_driver = { 20541c47d8cSEnrico Mioso .name = "huawei_cdc_ncm", 20641c47d8cSEnrico Mioso .id_table = huawei_cdc_ncm_devs, 20741c47d8cSEnrico Mioso .probe = usbnet_probe, 20841c47d8cSEnrico Mioso .disconnect = usbnet_disconnect, 20941c47d8cSEnrico Mioso .suspend = huawei_cdc_ncm_suspend, 21041c47d8cSEnrico Mioso .resume = huawei_cdc_ncm_resume, 21141c47d8cSEnrico Mioso .reset_resume = huawei_cdc_ncm_resume, 21241c47d8cSEnrico Mioso .supports_autosuspend = 1, 21341c47d8cSEnrico Mioso .disable_hub_initiated_lpm = 1, 21441c47d8cSEnrico Mioso }; 21541c47d8cSEnrico Mioso module_usb_driver(huawei_cdc_ncm_driver); 21641c47d8cSEnrico Mioso MODULE_AUTHOR("Enrico Mioso <mrkiko.rs@gmail.com>"); 21741c47d8cSEnrico Mioso MODULE_DESCRIPTION("USB CDC NCM host driver with encapsulated protocol support"); 21841c47d8cSEnrico Mioso MODULE_LICENSE("GPL"); 219