1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only 241c47d8cSEnrico Mioso /* huawei_cdc_ncm.c - handles Huawei devices using the CDC NCM protocol as 341c47d8cSEnrico Mioso * transport layer. 441c47d8cSEnrico Mioso * Copyright (C) 2013 Enrico Mioso <mrkiko.rs@gmail.com> 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 1641c47d8cSEnrico Mioso #include <linux/module.h> 1741c47d8cSEnrico Mioso #include <linux/netdevice.h> 1841c47d8cSEnrico Mioso #include <linux/ethtool.h> 1941c47d8cSEnrico Mioso #include <linux/if_vlan.h> 2041c47d8cSEnrico Mioso #include <linux/ip.h> 2141c47d8cSEnrico Mioso #include <linux/mii.h> 2241c47d8cSEnrico Mioso #include <linux/usb.h> 2341c47d8cSEnrico Mioso #include <linux/usb/cdc.h> 2441c47d8cSEnrico Mioso #include <linux/usb/usbnet.h> 2541c47d8cSEnrico Mioso #include <linux/usb/cdc-wdm.h> 2641c47d8cSEnrico Mioso #include <linux/usb/cdc_ncm.h> 2741c47d8cSEnrico Mioso 2841c47d8cSEnrico Mioso /* Driver data */ 2941c47d8cSEnrico Mioso struct huawei_cdc_ncm_state { 3041c47d8cSEnrico Mioso struct cdc_ncm_ctx *ctx; 3141c47d8cSEnrico Mioso atomic_t pmcount; 3241c47d8cSEnrico Mioso struct usb_driver *subdriver; 3341c47d8cSEnrico Mioso struct usb_interface *control; 3441c47d8cSEnrico Mioso struct usb_interface *data; 3541c47d8cSEnrico Mioso }; 3641c47d8cSEnrico Mioso 3741c47d8cSEnrico Mioso static int huawei_cdc_ncm_manage_power(struct usbnet *usbnet_dev, int on) 3841c47d8cSEnrico Mioso { 3941c47d8cSEnrico Mioso struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data; 4041c47d8cSEnrico Mioso int rv; 4141c47d8cSEnrico Mioso 4241c47d8cSEnrico Mioso if ((on && atomic_add_return(1, &drvstate->pmcount) == 1) || 4341c47d8cSEnrico Mioso (!on && atomic_dec_and_test(&drvstate->pmcount))) { 4441c47d8cSEnrico Mioso rv = usb_autopm_get_interface(usbnet_dev->intf); 4541c47d8cSEnrico Mioso usbnet_dev->intf->needs_remote_wakeup = on; 4641c47d8cSEnrico Mioso if (!rv) 4741c47d8cSEnrico Mioso usb_autopm_put_interface(usbnet_dev->intf); 4841c47d8cSEnrico Mioso } 4941c47d8cSEnrico Mioso return 0; 5041c47d8cSEnrico Mioso } 5141c47d8cSEnrico Mioso 5241c47d8cSEnrico Mioso static int huawei_cdc_ncm_wdm_manage_power(struct usb_interface *intf, 5341c47d8cSEnrico Mioso int status) 5441c47d8cSEnrico Mioso { 5541c47d8cSEnrico Mioso struct usbnet *usbnet_dev = usb_get_intfdata(intf); 5641c47d8cSEnrico Mioso 5741c47d8cSEnrico Mioso /* can be called while disconnecting */ 5841c47d8cSEnrico Mioso if (!usbnet_dev) 5941c47d8cSEnrico Mioso return 0; 6041c47d8cSEnrico Mioso 6141c47d8cSEnrico Mioso return huawei_cdc_ncm_manage_power(usbnet_dev, status); 6241c47d8cSEnrico Mioso } 6341c47d8cSEnrico Mioso 6441c47d8cSEnrico Mioso 6541c47d8cSEnrico Mioso static int huawei_cdc_ncm_bind(struct usbnet *usbnet_dev, 6641c47d8cSEnrico Mioso struct usb_interface *intf) 6741c47d8cSEnrico Mioso { 6841c47d8cSEnrico Mioso struct cdc_ncm_ctx *ctx; 6941c47d8cSEnrico Mioso struct usb_driver *subdriver = ERR_PTR(-ENODEV); 7041c47d8cSEnrico Mioso int ret = -ENODEV; 7141c47d8cSEnrico Mioso struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data; 724a0e3e98SEnrico Mioso int drvflags = 0; 7341c47d8cSEnrico Mioso 7441c47d8cSEnrico Mioso /* altsetting should always be 1 for NCM devices - so we hard-coded 754a0e3e98SEnrico Mioso * it here. Some huawei devices will need the NDP part of the NCM package to 764a0e3e98SEnrico Mioso * be at the end of the frame. 7741c47d8cSEnrico Mioso */ 784a0e3e98SEnrico Mioso drvflags |= CDC_NCM_FLAG_NDP_TO_END; 792b02c20cSEnrico Mioso 80*0fa81b30SAlexander Bersenev /* For many Huawei devices the NTB32 mode is the default and the best mode 81*0fa81b30SAlexander Bersenev * they work with. Huawei E5785 and E5885 devices refuse to work in NTB16 mode at all. 822b02c20cSEnrico Mioso */ 83*0fa81b30SAlexander Bersenev drvflags |= CDC_NCM_FLAG_PREFER_NTB32; 84*0fa81b30SAlexander Bersenev 854a0e3e98SEnrico Mioso ret = cdc_ncm_bind_common(usbnet_dev, intf, 1, drvflags); 8641c47d8cSEnrico Mioso if (ret) 8741c47d8cSEnrico Mioso goto err; 8841c47d8cSEnrico Mioso 8941c47d8cSEnrico Mioso ctx = drvstate->ctx; 9041c47d8cSEnrico Mioso 9141c47d8cSEnrico Mioso if (usbnet_dev->status) 923acc7461SBjørn Mork /* The wMaxCommand buffer must be big enough to hold 933acc7461SBjørn Mork * any message from the modem. Experience has shown 943acc7461SBjørn Mork * that some replies are more than 256 bytes long 9541c47d8cSEnrico Mioso */ 9641c47d8cSEnrico Mioso subdriver = usb_cdc_wdm_register(ctx->control, 9741c47d8cSEnrico Mioso &usbnet_dev->status->desc, 983acc7461SBjørn Mork 1024, /* wMaxCommand */ 9941c47d8cSEnrico Mioso huawei_cdc_ncm_wdm_manage_power); 10041c47d8cSEnrico Mioso if (IS_ERR(subdriver)) { 10141c47d8cSEnrico Mioso ret = PTR_ERR(subdriver); 10241c47d8cSEnrico Mioso cdc_ncm_unbind(usbnet_dev, intf); 10341c47d8cSEnrico Mioso goto err; 10441c47d8cSEnrico Mioso } 10541c47d8cSEnrico Mioso 10641c47d8cSEnrico Mioso /* Prevent usbnet from using the status descriptor */ 10741c47d8cSEnrico Mioso usbnet_dev->status = NULL; 10841c47d8cSEnrico Mioso 10941c47d8cSEnrico Mioso drvstate->subdriver = subdriver; 11041c47d8cSEnrico Mioso 11141c47d8cSEnrico Mioso err: 11241c47d8cSEnrico Mioso return ret; 11341c47d8cSEnrico Mioso } 11441c47d8cSEnrico Mioso 11541c47d8cSEnrico Mioso static void huawei_cdc_ncm_unbind(struct usbnet *usbnet_dev, 11641c47d8cSEnrico Mioso struct usb_interface *intf) 11741c47d8cSEnrico Mioso { 11841c47d8cSEnrico Mioso struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data; 11941c47d8cSEnrico Mioso struct cdc_ncm_ctx *ctx = drvstate->ctx; 12041c47d8cSEnrico Mioso 12141c47d8cSEnrico Mioso if (drvstate->subdriver && drvstate->subdriver->disconnect) 12241c47d8cSEnrico Mioso drvstate->subdriver->disconnect(ctx->control); 12341c47d8cSEnrico Mioso drvstate->subdriver = NULL; 12441c47d8cSEnrico Mioso 12541c47d8cSEnrico Mioso cdc_ncm_unbind(usbnet_dev, intf); 12641c47d8cSEnrico Mioso } 12741c47d8cSEnrico Mioso 12841c47d8cSEnrico Mioso static int huawei_cdc_ncm_suspend(struct usb_interface *intf, 12941c47d8cSEnrico Mioso pm_message_t message) 13041c47d8cSEnrico Mioso { 13141c47d8cSEnrico Mioso int ret = 0; 13241c47d8cSEnrico Mioso struct usbnet *usbnet_dev = usb_get_intfdata(intf); 13341c47d8cSEnrico Mioso struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data; 13441c47d8cSEnrico Mioso struct cdc_ncm_ctx *ctx = drvstate->ctx; 13541c47d8cSEnrico Mioso 13641c47d8cSEnrico Mioso if (ctx == NULL) { 13741c47d8cSEnrico Mioso ret = -ENODEV; 13841c47d8cSEnrico Mioso goto error; 13941c47d8cSEnrico Mioso } 14041c47d8cSEnrico Mioso 14141c47d8cSEnrico Mioso ret = usbnet_suspend(intf, message); 14241c47d8cSEnrico Mioso if (ret < 0) 14341c47d8cSEnrico Mioso goto error; 14441c47d8cSEnrico Mioso 14541c47d8cSEnrico Mioso if (intf == ctx->control && 14641c47d8cSEnrico Mioso drvstate->subdriver && 14741c47d8cSEnrico Mioso drvstate->subdriver->suspend) 14841c47d8cSEnrico Mioso ret = drvstate->subdriver->suspend(intf, message); 14941c47d8cSEnrico Mioso if (ret < 0) 15041c47d8cSEnrico Mioso usbnet_resume(intf); 15141c47d8cSEnrico Mioso 15241c47d8cSEnrico Mioso error: 15341c47d8cSEnrico Mioso return ret; 15441c47d8cSEnrico Mioso } 15541c47d8cSEnrico Mioso 15641c47d8cSEnrico Mioso static int huawei_cdc_ncm_resume(struct usb_interface *intf) 15741c47d8cSEnrico Mioso { 15841c47d8cSEnrico Mioso int ret = 0; 15941c47d8cSEnrico Mioso struct usbnet *usbnet_dev = usb_get_intfdata(intf); 16041c47d8cSEnrico Mioso struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data; 16141c47d8cSEnrico Mioso bool callsub; 16241c47d8cSEnrico Mioso struct cdc_ncm_ctx *ctx = drvstate->ctx; 16341c47d8cSEnrico Mioso 16441c47d8cSEnrico Mioso /* should we call subdriver's resume function? */ 16541c47d8cSEnrico Mioso callsub = 16641c47d8cSEnrico Mioso (intf == ctx->control && 16741c47d8cSEnrico Mioso drvstate->subdriver && 16841c47d8cSEnrico Mioso drvstate->subdriver->resume); 16941c47d8cSEnrico Mioso 17041c47d8cSEnrico Mioso if (callsub) 17141c47d8cSEnrico Mioso ret = drvstate->subdriver->resume(intf); 17241c47d8cSEnrico Mioso if (ret < 0) 17341c47d8cSEnrico Mioso goto err; 17441c47d8cSEnrico Mioso ret = usbnet_resume(intf); 17541c47d8cSEnrico Mioso if (ret < 0 && callsub) 17641c47d8cSEnrico Mioso drvstate->subdriver->suspend(intf, PMSG_SUSPEND); 17741c47d8cSEnrico Mioso err: 17841c47d8cSEnrico Mioso return ret; 17941c47d8cSEnrico Mioso } 18041c47d8cSEnrico Mioso 18141c47d8cSEnrico Mioso static const struct driver_info huawei_cdc_ncm_info = { 18241c47d8cSEnrico Mioso .description = "Huawei CDC NCM device", 18341c47d8cSEnrico Mioso .flags = FLAG_NO_SETINT | FLAG_MULTI_PACKET | FLAG_WWAN, 18441c47d8cSEnrico Mioso .bind = huawei_cdc_ncm_bind, 18541c47d8cSEnrico Mioso .unbind = huawei_cdc_ncm_unbind, 18641c47d8cSEnrico Mioso .manage_power = huawei_cdc_ncm_manage_power, 18741c47d8cSEnrico Mioso .rx_fixup = cdc_ncm_rx_fixup, 18841c47d8cSEnrico Mioso .tx_fixup = cdc_ncm_tx_fixup, 18941c47d8cSEnrico Mioso }; 19041c47d8cSEnrico Mioso 19141c47d8cSEnrico Mioso static const struct usb_device_id huawei_cdc_ncm_devs[] = { 19241c47d8cSEnrico Mioso /* Huawei NCM devices disguised as vendor specific */ 19341c47d8cSEnrico Mioso { USB_VENDOR_AND_INTERFACE_INFO(0x12d1, 0xff, 0x02, 0x16), 19441c47d8cSEnrico Mioso .driver_info = (unsigned long)&huawei_cdc_ncm_info, 19541c47d8cSEnrico Mioso }, 19641c47d8cSEnrico Mioso { USB_VENDOR_AND_INTERFACE_INFO(0x12d1, 0xff, 0x02, 0x46), 19741c47d8cSEnrico Mioso .driver_info = (unsigned long)&huawei_cdc_ncm_info, 19841c47d8cSEnrico Mioso }, 19941c47d8cSEnrico Mioso { USB_VENDOR_AND_INTERFACE_INFO(0x12d1, 0xff, 0x02, 0x76), 20041c47d8cSEnrico Mioso .driver_info = (unsigned long)&huawei_cdc_ncm_info, 20141c47d8cSEnrico Mioso }, 202c2a6c781SBjørn Mork { USB_VENDOR_AND_INTERFACE_INFO(0x12d1, 0xff, 0x03, 0x16), 203c2a6c781SBjørn Mork .driver_info = (unsigned long)&huawei_cdc_ncm_info, 204c2a6c781SBjørn Mork }, 20541c47d8cSEnrico Mioso 20641c47d8cSEnrico Mioso /* Terminating entry */ 20741c47d8cSEnrico Mioso { 20841c47d8cSEnrico Mioso }, 20941c47d8cSEnrico Mioso }; 21041c47d8cSEnrico Mioso MODULE_DEVICE_TABLE(usb, huawei_cdc_ncm_devs); 21141c47d8cSEnrico Mioso 21241c47d8cSEnrico Mioso static struct usb_driver huawei_cdc_ncm_driver = { 21341c47d8cSEnrico Mioso .name = "huawei_cdc_ncm", 21441c47d8cSEnrico Mioso .id_table = huawei_cdc_ncm_devs, 21541c47d8cSEnrico Mioso .probe = usbnet_probe, 21641c47d8cSEnrico Mioso .disconnect = usbnet_disconnect, 21741c47d8cSEnrico Mioso .suspend = huawei_cdc_ncm_suspend, 21841c47d8cSEnrico Mioso .resume = huawei_cdc_ncm_resume, 21941c47d8cSEnrico Mioso .reset_resume = huawei_cdc_ncm_resume, 22041c47d8cSEnrico Mioso .supports_autosuspend = 1, 22141c47d8cSEnrico Mioso .disable_hub_initiated_lpm = 1, 22241c47d8cSEnrico Mioso }; 22341c47d8cSEnrico Mioso module_usb_driver(huawei_cdc_ncm_driver); 22441c47d8cSEnrico Mioso MODULE_AUTHOR("Enrico Mioso <mrkiko.rs@gmail.com>"); 22541c47d8cSEnrico Mioso MODULE_DESCRIPTION("USB CDC NCM host driver with encapsulated protocol support"); 22641c47d8cSEnrico Mioso MODULE_LICENSE("GPL"); 227