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; 76*4a0e3e98SEnrico Mioso int drvflags = 0; 7741c47d8cSEnrico Mioso 7841c47d8cSEnrico Mioso /* altsetting should always be 1 for NCM devices - so we hard-coded 79*4a0e3e98SEnrico Mioso * it here. Some huawei devices will need the NDP part of the NCM package to 80*4a0e3e98SEnrico Mioso * be at the end of the frame. 8141c47d8cSEnrico Mioso */ 82*4a0e3e98SEnrico Mioso drvflags |= CDC_NCM_FLAG_NDP_TO_END; 83*4a0e3e98SEnrico Mioso ret = cdc_ncm_bind_common(usbnet_dev, intf, 1, drvflags); 8441c47d8cSEnrico Mioso if (ret) 8541c47d8cSEnrico Mioso goto err; 8641c47d8cSEnrico Mioso 8741c47d8cSEnrico Mioso ctx = drvstate->ctx; 8841c47d8cSEnrico Mioso 8941c47d8cSEnrico Mioso if (usbnet_dev->status) 903acc7461SBjørn Mork /* The wMaxCommand buffer must be big enough to hold 913acc7461SBjørn Mork * any message from the modem. Experience has shown 923acc7461SBjørn Mork * that some replies are more than 256 bytes long 9341c47d8cSEnrico Mioso */ 9441c47d8cSEnrico Mioso subdriver = usb_cdc_wdm_register(ctx->control, 9541c47d8cSEnrico Mioso &usbnet_dev->status->desc, 963acc7461SBjørn Mork 1024, /* wMaxCommand */ 9741c47d8cSEnrico Mioso huawei_cdc_ncm_wdm_manage_power); 9841c47d8cSEnrico Mioso if (IS_ERR(subdriver)) { 9941c47d8cSEnrico Mioso ret = PTR_ERR(subdriver); 10041c47d8cSEnrico Mioso cdc_ncm_unbind(usbnet_dev, intf); 10141c47d8cSEnrico Mioso goto err; 10241c47d8cSEnrico Mioso } 10341c47d8cSEnrico Mioso 10441c47d8cSEnrico Mioso /* Prevent usbnet from using the status descriptor */ 10541c47d8cSEnrico Mioso usbnet_dev->status = NULL; 10641c47d8cSEnrico Mioso 10741c47d8cSEnrico Mioso drvstate->subdriver = subdriver; 10841c47d8cSEnrico Mioso 10941c47d8cSEnrico Mioso err: 11041c47d8cSEnrico Mioso return ret; 11141c47d8cSEnrico Mioso } 11241c47d8cSEnrico Mioso 11341c47d8cSEnrico Mioso static void huawei_cdc_ncm_unbind(struct usbnet *usbnet_dev, 11441c47d8cSEnrico Mioso struct usb_interface *intf) 11541c47d8cSEnrico Mioso { 11641c47d8cSEnrico Mioso struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data; 11741c47d8cSEnrico Mioso struct cdc_ncm_ctx *ctx = drvstate->ctx; 11841c47d8cSEnrico Mioso 11941c47d8cSEnrico Mioso if (drvstate->subdriver && drvstate->subdriver->disconnect) 12041c47d8cSEnrico Mioso drvstate->subdriver->disconnect(ctx->control); 12141c47d8cSEnrico Mioso drvstate->subdriver = NULL; 12241c47d8cSEnrico Mioso 12341c47d8cSEnrico Mioso cdc_ncm_unbind(usbnet_dev, intf); 12441c47d8cSEnrico Mioso } 12541c47d8cSEnrico Mioso 12641c47d8cSEnrico Mioso static int huawei_cdc_ncm_suspend(struct usb_interface *intf, 12741c47d8cSEnrico Mioso pm_message_t message) 12841c47d8cSEnrico Mioso { 12941c47d8cSEnrico Mioso int ret = 0; 13041c47d8cSEnrico Mioso struct usbnet *usbnet_dev = usb_get_intfdata(intf); 13141c47d8cSEnrico Mioso struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data; 13241c47d8cSEnrico Mioso struct cdc_ncm_ctx *ctx = drvstate->ctx; 13341c47d8cSEnrico Mioso 13441c47d8cSEnrico Mioso if (ctx == NULL) { 13541c47d8cSEnrico Mioso ret = -ENODEV; 13641c47d8cSEnrico Mioso goto error; 13741c47d8cSEnrico Mioso } 13841c47d8cSEnrico Mioso 13941c47d8cSEnrico Mioso ret = usbnet_suspend(intf, message); 14041c47d8cSEnrico Mioso if (ret < 0) 14141c47d8cSEnrico Mioso goto error; 14241c47d8cSEnrico Mioso 14341c47d8cSEnrico Mioso if (intf == ctx->control && 14441c47d8cSEnrico Mioso drvstate->subdriver && 14541c47d8cSEnrico Mioso drvstate->subdriver->suspend) 14641c47d8cSEnrico Mioso ret = drvstate->subdriver->suspend(intf, message); 14741c47d8cSEnrico Mioso if (ret < 0) 14841c47d8cSEnrico Mioso usbnet_resume(intf); 14941c47d8cSEnrico Mioso 15041c47d8cSEnrico Mioso error: 15141c47d8cSEnrico Mioso return ret; 15241c47d8cSEnrico Mioso } 15341c47d8cSEnrico Mioso 15441c47d8cSEnrico Mioso static int huawei_cdc_ncm_resume(struct usb_interface *intf) 15541c47d8cSEnrico Mioso { 15641c47d8cSEnrico Mioso int ret = 0; 15741c47d8cSEnrico Mioso struct usbnet *usbnet_dev = usb_get_intfdata(intf); 15841c47d8cSEnrico Mioso struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data; 15941c47d8cSEnrico Mioso bool callsub; 16041c47d8cSEnrico Mioso struct cdc_ncm_ctx *ctx = drvstate->ctx; 16141c47d8cSEnrico Mioso 16241c47d8cSEnrico Mioso /* should we call subdriver's resume function? */ 16341c47d8cSEnrico Mioso callsub = 16441c47d8cSEnrico Mioso (intf == ctx->control && 16541c47d8cSEnrico Mioso drvstate->subdriver && 16641c47d8cSEnrico Mioso drvstate->subdriver->resume); 16741c47d8cSEnrico Mioso 16841c47d8cSEnrico Mioso if (callsub) 16941c47d8cSEnrico Mioso ret = drvstate->subdriver->resume(intf); 17041c47d8cSEnrico Mioso if (ret < 0) 17141c47d8cSEnrico Mioso goto err; 17241c47d8cSEnrico Mioso ret = usbnet_resume(intf); 17341c47d8cSEnrico Mioso if (ret < 0 && callsub) 17441c47d8cSEnrico Mioso drvstate->subdriver->suspend(intf, PMSG_SUSPEND); 17541c47d8cSEnrico Mioso err: 17641c47d8cSEnrico Mioso return ret; 17741c47d8cSEnrico Mioso } 17841c47d8cSEnrico Mioso 17941c47d8cSEnrico Mioso static const struct driver_info huawei_cdc_ncm_info = { 18041c47d8cSEnrico Mioso .description = "Huawei CDC NCM device", 18141c47d8cSEnrico Mioso .flags = FLAG_NO_SETINT | FLAG_MULTI_PACKET | FLAG_WWAN, 18241c47d8cSEnrico Mioso .bind = huawei_cdc_ncm_bind, 18341c47d8cSEnrico Mioso .unbind = huawei_cdc_ncm_unbind, 18441c47d8cSEnrico Mioso .manage_power = huawei_cdc_ncm_manage_power, 18541c47d8cSEnrico Mioso .rx_fixup = cdc_ncm_rx_fixup, 18641c47d8cSEnrico Mioso .tx_fixup = cdc_ncm_tx_fixup, 18741c47d8cSEnrico Mioso }; 18841c47d8cSEnrico Mioso 18941c47d8cSEnrico Mioso static const struct usb_device_id huawei_cdc_ncm_devs[] = { 19041c47d8cSEnrico Mioso /* Huawei NCM devices disguised as vendor specific */ 19141c47d8cSEnrico Mioso { USB_VENDOR_AND_INTERFACE_INFO(0x12d1, 0xff, 0x02, 0x16), 19241c47d8cSEnrico Mioso .driver_info = (unsigned long)&huawei_cdc_ncm_info, 19341c47d8cSEnrico Mioso }, 19441c47d8cSEnrico Mioso { USB_VENDOR_AND_INTERFACE_INFO(0x12d1, 0xff, 0x02, 0x46), 19541c47d8cSEnrico Mioso .driver_info = (unsigned long)&huawei_cdc_ncm_info, 19641c47d8cSEnrico Mioso }, 19741c47d8cSEnrico Mioso { USB_VENDOR_AND_INTERFACE_INFO(0x12d1, 0xff, 0x02, 0x76), 19841c47d8cSEnrico Mioso .driver_info = (unsigned long)&huawei_cdc_ncm_info, 19941c47d8cSEnrico Mioso }, 200c2a6c781SBjørn Mork { USB_VENDOR_AND_INTERFACE_INFO(0x12d1, 0xff, 0x03, 0x16), 201c2a6c781SBjørn Mork .driver_info = (unsigned long)&huawei_cdc_ncm_info, 202c2a6c781SBjørn Mork }, 20341c47d8cSEnrico Mioso 20441c47d8cSEnrico Mioso /* Terminating entry */ 20541c47d8cSEnrico Mioso { 20641c47d8cSEnrico Mioso }, 20741c47d8cSEnrico Mioso }; 20841c47d8cSEnrico Mioso MODULE_DEVICE_TABLE(usb, huawei_cdc_ncm_devs); 20941c47d8cSEnrico Mioso 21041c47d8cSEnrico Mioso static struct usb_driver huawei_cdc_ncm_driver = { 21141c47d8cSEnrico Mioso .name = "huawei_cdc_ncm", 21241c47d8cSEnrico Mioso .id_table = huawei_cdc_ncm_devs, 21341c47d8cSEnrico Mioso .probe = usbnet_probe, 21441c47d8cSEnrico Mioso .disconnect = usbnet_disconnect, 21541c47d8cSEnrico Mioso .suspend = huawei_cdc_ncm_suspend, 21641c47d8cSEnrico Mioso .resume = huawei_cdc_ncm_resume, 21741c47d8cSEnrico Mioso .reset_resume = huawei_cdc_ncm_resume, 21841c47d8cSEnrico Mioso .supports_autosuspend = 1, 21941c47d8cSEnrico Mioso .disable_hub_initiated_lpm = 1, 22041c47d8cSEnrico Mioso }; 22141c47d8cSEnrico Mioso module_usb_driver(huawei_cdc_ncm_driver); 22241c47d8cSEnrico Mioso MODULE_AUTHOR("Enrico Mioso <mrkiko.rs@gmail.com>"); 22341c47d8cSEnrico Mioso MODULE_DESCRIPTION("USB CDC NCM host driver with encapsulated protocol support"); 22441c47d8cSEnrico Mioso MODULE_LICENSE("GPL"); 225