18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* huawei_cdc_ncm.c - handles Huawei devices using the CDC NCM protocol as 38c2ecf20Sopenharmony_ci * transport layer. 48c2ecf20Sopenharmony_ci * Copyright (C) 2013 Enrico Mioso <mrkiko.rs@gmail.com> 58c2ecf20Sopenharmony_ci * 68c2ecf20Sopenharmony_ci * ABSTRACT: 78c2ecf20Sopenharmony_ci * This driver handles devices resembling the CDC NCM standard, but 88c2ecf20Sopenharmony_ci * encapsulating another protocol inside it. An example are some Huawei 3G 98c2ecf20Sopenharmony_ci * devices, exposing an embedded AT channel where you can set up the NCM 108c2ecf20Sopenharmony_ci * connection. 118c2ecf20Sopenharmony_ci * This code has been heavily inspired by the cdc_mbim.c driver, which is 128c2ecf20Sopenharmony_ci * Copyright (c) 2012 Smith Micro Software, Inc. 138c2ecf20Sopenharmony_ci * Copyright (c) 2012 Bjørn Mork <bjorn@mork.no> 148c2ecf20Sopenharmony_ci */ 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_ci#include <linux/module.h> 178c2ecf20Sopenharmony_ci#include <linux/netdevice.h> 188c2ecf20Sopenharmony_ci#include <linux/ethtool.h> 198c2ecf20Sopenharmony_ci#include <linux/if_vlan.h> 208c2ecf20Sopenharmony_ci#include <linux/ip.h> 218c2ecf20Sopenharmony_ci#include <linux/mii.h> 228c2ecf20Sopenharmony_ci#include <linux/usb.h> 238c2ecf20Sopenharmony_ci#include <linux/usb/cdc.h> 248c2ecf20Sopenharmony_ci#include <linux/usb/usbnet.h> 258c2ecf20Sopenharmony_ci#include <linux/usb/cdc-wdm.h> 268c2ecf20Sopenharmony_ci#include <linux/usb/cdc_ncm.h> 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci/* Driver data */ 298c2ecf20Sopenharmony_cistruct huawei_cdc_ncm_state { 308c2ecf20Sopenharmony_ci struct cdc_ncm_ctx *ctx; 318c2ecf20Sopenharmony_ci atomic_t pmcount; 328c2ecf20Sopenharmony_ci struct usb_driver *subdriver; 338c2ecf20Sopenharmony_ci struct usb_interface *control; 348c2ecf20Sopenharmony_ci struct usb_interface *data; 358c2ecf20Sopenharmony_ci}; 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_cistatic int huawei_cdc_ncm_manage_power(struct usbnet *usbnet_dev, int on) 388c2ecf20Sopenharmony_ci{ 398c2ecf20Sopenharmony_ci struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data; 408c2ecf20Sopenharmony_ci int rv; 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci if ((on && atomic_add_return(1, &drvstate->pmcount) == 1) || 438c2ecf20Sopenharmony_ci (!on && atomic_dec_and_test(&drvstate->pmcount))) { 448c2ecf20Sopenharmony_ci rv = usb_autopm_get_interface(usbnet_dev->intf); 458c2ecf20Sopenharmony_ci usbnet_dev->intf->needs_remote_wakeup = on; 468c2ecf20Sopenharmony_ci if (!rv) 478c2ecf20Sopenharmony_ci usb_autopm_put_interface(usbnet_dev->intf); 488c2ecf20Sopenharmony_ci } 498c2ecf20Sopenharmony_ci return 0; 508c2ecf20Sopenharmony_ci} 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_cistatic int huawei_cdc_ncm_wdm_manage_power(struct usb_interface *intf, 538c2ecf20Sopenharmony_ci int status) 548c2ecf20Sopenharmony_ci{ 558c2ecf20Sopenharmony_ci struct usbnet *usbnet_dev = usb_get_intfdata(intf); 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_ci /* can be called while disconnecting */ 588c2ecf20Sopenharmony_ci if (!usbnet_dev) 598c2ecf20Sopenharmony_ci return 0; 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_ci return huawei_cdc_ncm_manage_power(usbnet_dev, status); 628c2ecf20Sopenharmony_ci} 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_cistatic int huawei_cdc_ncm_bind(struct usbnet *usbnet_dev, 668c2ecf20Sopenharmony_ci struct usb_interface *intf) 678c2ecf20Sopenharmony_ci{ 688c2ecf20Sopenharmony_ci struct cdc_ncm_ctx *ctx; 698c2ecf20Sopenharmony_ci struct usb_driver *subdriver = ERR_PTR(-ENODEV); 708c2ecf20Sopenharmony_ci int ret; 718c2ecf20Sopenharmony_ci struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data; 728c2ecf20Sopenharmony_ci int drvflags = 0; 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci /* altsetting should always be 1 for NCM devices - so we hard-coded 758c2ecf20Sopenharmony_ci * it here. Some huawei devices will need the NDP part of the NCM package to 768c2ecf20Sopenharmony_ci * be at the end of the frame. 778c2ecf20Sopenharmony_ci */ 788c2ecf20Sopenharmony_ci drvflags |= CDC_NCM_FLAG_NDP_TO_END; 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci /* For many Huawei devices the NTB32 mode is the default and the best mode 818c2ecf20Sopenharmony_ci * they work with. Huawei E5785 and E5885 devices refuse to work in NTB16 mode at all. 828c2ecf20Sopenharmony_ci */ 838c2ecf20Sopenharmony_ci drvflags |= CDC_NCM_FLAG_PREFER_NTB32; 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci ret = cdc_ncm_bind_common(usbnet_dev, intf, 1, drvflags); 868c2ecf20Sopenharmony_ci if (ret) 878c2ecf20Sopenharmony_ci goto err; 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci ctx = drvstate->ctx; 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_ci if (usbnet_dev->status) 928c2ecf20Sopenharmony_ci /* The wMaxCommand buffer must be big enough to hold 938c2ecf20Sopenharmony_ci * any message from the modem. Experience has shown 948c2ecf20Sopenharmony_ci * that some replies are more than 256 bytes long 958c2ecf20Sopenharmony_ci */ 968c2ecf20Sopenharmony_ci subdriver = usb_cdc_wdm_register(ctx->control, 978c2ecf20Sopenharmony_ci &usbnet_dev->status->desc, 988c2ecf20Sopenharmony_ci 1024, /* wMaxCommand */ 998c2ecf20Sopenharmony_ci huawei_cdc_ncm_wdm_manage_power); 1008c2ecf20Sopenharmony_ci if (IS_ERR(subdriver)) { 1018c2ecf20Sopenharmony_ci ret = PTR_ERR(subdriver); 1028c2ecf20Sopenharmony_ci cdc_ncm_unbind(usbnet_dev, intf); 1038c2ecf20Sopenharmony_ci goto err; 1048c2ecf20Sopenharmony_ci } 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci /* Prevent usbnet from using the status descriptor */ 1078c2ecf20Sopenharmony_ci usbnet_dev->status = NULL; 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_ci drvstate->subdriver = subdriver; 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_cierr: 1128c2ecf20Sopenharmony_ci return ret; 1138c2ecf20Sopenharmony_ci} 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_cistatic void huawei_cdc_ncm_unbind(struct usbnet *usbnet_dev, 1168c2ecf20Sopenharmony_ci struct usb_interface *intf) 1178c2ecf20Sopenharmony_ci{ 1188c2ecf20Sopenharmony_ci struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data; 1198c2ecf20Sopenharmony_ci struct cdc_ncm_ctx *ctx = drvstate->ctx; 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_ci if (drvstate->subdriver && drvstate->subdriver->disconnect) 1228c2ecf20Sopenharmony_ci drvstate->subdriver->disconnect(ctx->control); 1238c2ecf20Sopenharmony_ci drvstate->subdriver = NULL; 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_ci cdc_ncm_unbind(usbnet_dev, intf); 1268c2ecf20Sopenharmony_ci} 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_cistatic int huawei_cdc_ncm_suspend(struct usb_interface *intf, 1298c2ecf20Sopenharmony_ci pm_message_t message) 1308c2ecf20Sopenharmony_ci{ 1318c2ecf20Sopenharmony_ci int ret = 0; 1328c2ecf20Sopenharmony_ci struct usbnet *usbnet_dev = usb_get_intfdata(intf); 1338c2ecf20Sopenharmony_ci struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data; 1348c2ecf20Sopenharmony_ci struct cdc_ncm_ctx *ctx = drvstate->ctx; 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci if (ctx == NULL) { 1378c2ecf20Sopenharmony_ci ret = -ENODEV; 1388c2ecf20Sopenharmony_ci goto error; 1398c2ecf20Sopenharmony_ci } 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci ret = usbnet_suspend(intf, message); 1428c2ecf20Sopenharmony_ci if (ret < 0) 1438c2ecf20Sopenharmony_ci goto error; 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_ci if (intf == ctx->control && 1468c2ecf20Sopenharmony_ci drvstate->subdriver && 1478c2ecf20Sopenharmony_ci drvstate->subdriver->suspend) 1488c2ecf20Sopenharmony_ci ret = drvstate->subdriver->suspend(intf, message); 1498c2ecf20Sopenharmony_ci if (ret < 0) 1508c2ecf20Sopenharmony_ci usbnet_resume(intf); 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_cierror: 1538c2ecf20Sopenharmony_ci return ret; 1548c2ecf20Sopenharmony_ci} 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_cistatic int huawei_cdc_ncm_resume(struct usb_interface *intf) 1578c2ecf20Sopenharmony_ci{ 1588c2ecf20Sopenharmony_ci int ret = 0; 1598c2ecf20Sopenharmony_ci struct usbnet *usbnet_dev = usb_get_intfdata(intf); 1608c2ecf20Sopenharmony_ci struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data; 1618c2ecf20Sopenharmony_ci bool callsub; 1628c2ecf20Sopenharmony_ci struct cdc_ncm_ctx *ctx = drvstate->ctx; 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_ci /* should we call subdriver's resume function? */ 1658c2ecf20Sopenharmony_ci callsub = 1668c2ecf20Sopenharmony_ci (intf == ctx->control && 1678c2ecf20Sopenharmony_ci drvstate->subdriver && 1688c2ecf20Sopenharmony_ci drvstate->subdriver->resume); 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_ci if (callsub) 1718c2ecf20Sopenharmony_ci ret = drvstate->subdriver->resume(intf); 1728c2ecf20Sopenharmony_ci if (ret < 0) 1738c2ecf20Sopenharmony_ci goto err; 1748c2ecf20Sopenharmony_ci ret = usbnet_resume(intf); 1758c2ecf20Sopenharmony_ci if (ret < 0 && callsub) 1768c2ecf20Sopenharmony_ci drvstate->subdriver->suspend(intf, PMSG_SUSPEND); 1778c2ecf20Sopenharmony_cierr: 1788c2ecf20Sopenharmony_ci return ret; 1798c2ecf20Sopenharmony_ci} 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_cistatic const struct driver_info huawei_cdc_ncm_info = { 1828c2ecf20Sopenharmony_ci .description = "Huawei CDC NCM device", 1838c2ecf20Sopenharmony_ci .flags = FLAG_NO_SETINT | FLAG_MULTI_PACKET | FLAG_WWAN, 1848c2ecf20Sopenharmony_ci .bind = huawei_cdc_ncm_bind, 1858c2ecf20Sopenharmony_ci .unbind = huawei_cdc_ncm_unbind, 1868c2ecf20Sopenharmony_ci .manage_power = huawei_cdc_ncm_manage_power, 1878c2ecf20Sopenharmony_ci .rx_fixup = cdc_ncm_rx_fixup, 1888c2ecf20Sopenharmony_ci .tx_fixup = cdc_ncm_tx_fixup, 1898c2ecf20Sopenharmony_ci}; 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_cistatic const struct usb_device_id huawei_cdc_ncm_devs[] = { 1928c2ecf20Sopenharmony_ci /* Huawei NCM devices disguised as vendor specific */ 1938c2ecf20Sopenharmony_ci { USB_VENDOR_AND_INTERFACE_INFO(0x12d1, 0xff, 0x02, 0x16), 1948c2ecf20Sopenharmony_ci .driver_info = (unsigned long)&huawei_cdc_ncm_info, 1958c2ecf20Sopenharmony_ci }, 1968c2ecf20Sopenharmony_ci { USB_VENDOR_AND_INTERFACE_INFO(0x12d1, 0xff, 0x02, 0x46), 1978c2ecf20Sopenharmony_ci .driver_info = (unsigned long)&huawei_cdc_ncm_info, 1988c2ecf20Sopenharmony_ci }, 1998c2ecf20Sopenharmony_ci { USB_VENDOR_AND_INTERFACE_INFO(0x12d1, 0xff, 0x02, 0x76), 2008c2ecf20Sopenharmony_ci .driver_info = (unsigned long)&huawei_cdc_ncm_info, 2018c2ecf20Sopenharmony_ci }, 2028c2ecf20Sopenharmony_ci { USB_VENDOR_AND_INTERFACE_INFO(0x12d1, 0xff, 0x03, 0x16), 2038c2ecf20Sopenharmony_ci .driver_info = (unsigned long)&huawei_cdc_ncm_info, 2048c2ecf20Sopenharmony_ci }, 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_ci /* Terminating entry */ 2078c2ecf20Sopenharmony_ci { 2088c2ecf20Sopenharmony_ci }, 2098c2ecf20Sopenharmony_ci}; 2108c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(usb, huawei_cdc_ncm_devs); 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_cistatic struct usb_driver huawei_cdc_ncm_driver = { 2138c2ecf20Sopenharmony_ci .name = "huawei_cdc_ncm", 2148c2ecf20Sopenharmony_ci .id_table = huawei_cdc_ncm_devs, 2158c2ecf20Sopenharmony_ci .probe = usbnet_probe, 2168c2ecf20Sopenharmony_ci .disconnect = usbnet_disconnect, 2178c2ecf20Sopenharmony_ci .suspend = huawei_cdc_ncm_suspend, 2188c2ecf20Sopenharmony_ci .resume = huawei_cdc_ncm_resume, 2198c2ecf20Sopenharmony_ci .reset_resume = huawei_cdc_ncm_resume, 2208c2ecf20Sopenharmony_ci .supports_autosuspend = 1, 2218c2ecf20Sopenharmony_ci .disable_hub_initiated_lpm = 1, 2228c2ecf20Sopenharmony_ci}; 2238c2ecf20Sopenharmony_cimodule_usb_driver(huawei_cdc_ncm_driver); 2248c2ecf20Sopenharmony_ciMODULE_AUTHOR("Enrico Mioso <mrkiko.rs@gmail.com>"); 2258c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("USB CDC NCM host driver with encapsulated protocol support"); 2268c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 227