228 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/* huawei_cdc_ncm.c - handles Huawei devices using the CDC NCM protocol as3 * transport layer.4 * Copyright (C) 2013 Enrico Mioso <mrkiko.rs@gmail.com>5 *6 * ABSTRACT:7 * This driver handles devices resembling the CDC NCM standard, but8 * encapsulating another protocol inside it. An example are some Huawei 3G9 * devices, exposing an embedded AT channel where you can set up the NCM10 * connection.11 * This code has been heavily inspired by the cdc_mbim.c driver, which is12 * Copyright (c) 2012 Smith Micro Software, Inc.13 * Copyright (c) 2012 Bjørn Mork <bjorn@mork.no>14 */15 16#include <linux/module.h>17#include <linux/netdevice.h>18#include <linux/ethtool.h>19#include <linux/if_vlan.h>20#include <linux/ip.h>21#include <linux/mii.h>22#include <linux/usb.h>23#include <linux/usb/cdc.h>24#include <linux/usb/usbnet.h>25#include <linux/usb/cdc-wdm.h>26#include <linux/usb/cdc_ncm.h>27 28/* Driver data */29struct huawei_cdc_ncm_state {30 struct cdc_ncm_ctx *ctx;31 atomic_t pmcount;32 struct usb_driver *subdriver;33 struct usb_interface *control;34 struct usb_interface *data;35};36 37static int huawei_cdc_ncm_manage_power(struct usbnet *usbnet_dev, int on)38{39 struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data;40 int rv;41 42 if ((on && atomic_add_return(1, &drvstate->pmcount) == 1) ||43 (!on && atomic_dec_and_test(&drvstate->pmcount))) {44 rv = usb_autopm_get_interface(usbnet_dev->intf);45 usbnet_dev->intf->needs_remote_wakeup = on;46 if (!rv)47 usb_autopm_put_interface(usbnet_dev->intf);48 }49 return 0;50}51 52static int huawei_cdc_ncm_wdm_manage_power(struct usb_interface *intf,53 int status)54{55 struct usbnet *usbnet_dev = usb_get_intfdata(intf);56 57 /* can be called while disconnecting */58 if (!usbnet_dev)59 return 0;60 61 return huawei_cdc_ncm_manage_power(usbnet_dev, status);62}63 64 65static int huawei_cdc_ncm_bind(struct usbnet *usbnet_dev,66 struct usb_interface *intf)67{68 struct cdc_ncm_ctx *ctx;69 struct usb_driver *subdriver = ERR_PTR(-ENODEV);70 int ret;71 struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data;72 int drvflags = 0;73 74 /* altsetting should always be 1 for NCM devices - so we hard-coded75 * it here. Some huawei devices will need the NDP part of the NCM package to76 * be at the end of the frame.77 */78 drvflags |= CDC_NCM_FLAG_NDP_TO_END;79 80 /* For many Huawei devices the NTB32 mode is the default and the best mode81 * they work with. Huawei E5785 and E5885 devices refuse to work in NTB16 mode at all.82 */83 drvflags |= CDC_NCM_FLAG_PREFER_NTB32;84 85 ret = cdc_ncm_bind_common(usbnet_dev, intf, 1, drvflags);86 if (ret)87 goto err;88 89 ctx = drvstate->ctx;90 91 if (usbnet_dev->status)92 /* The wMaxCommand buffer must be big enough to hold93 * any message from the modem. Experience has shown94 * that some replies are more than 256 bytes long95 */96 subdriver = usb_cdc_wdm_register(ctx->control,97 &usbnet_dev->status->desc,98 1024, /* wMaxCommand */99 WWAN_PORT_AT,100 huawei_cdc_ncm_wdm_manage_power);101 if (IS_ERR(subdriver)) {102 ret = PTR_ERR(subdriver);103 cdc_ncm_unbind(usbnet_dev, intf);104 goto err;105 }106 107 /* Prevent usbnet from using the status descriptor */108 usbnet_dev->status = NULL;109 110 drvstate->subdriver = subdriver;111 112err:113 return ret;114}115 116static void huawei_cdc_ncm_unbind(struct usbnet *usbnet_dev,117 struct usb_interface *intf)118{119 struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data;120 struct cdc_ncm_ctx *ctx = drvstate->ctx;121 122 if (drvstate->subdriver && drvstate->subdriver->disconnect)123 drvstate->subdriver->disconnect(ctx->control);124 drvstate->subdriver = NULL;125 126 cdc_ncm_unbind(usbnet_dev, intf);127}128 129static int huawei_cdc_ncm_suspend(struct usb_interface *intf,130 pm_message_t message)131{132 int ret = 0;133 struct usbnet *usbnet_dev = usb_get_intfdata(intf);134 struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data;135 struct cdc_ncm_ctx *ctx = drvstate->ctx;136 137 if (ctx == NULL) {138 ret = -ENODEV;139 goto error;140 }141 142 ret = usbnet_suspend(intf, message);143 if (ret < 0)144 goto error;145 146 if (intf == ctx->control &&147 drvstate->subdriver &&148 drvstate->subdriver->suspend)149 ret = drvstate->subdriver->suspend(intf, message);150 if (ret < 0)151 usbnet_resume(intf);152 153error:154 return ret;155}156 157static int huawei_cdc_ncm_resume(struct usb_interface *intf)158{159 int ret = 0;160 struct usbnet *usbnet_dev = usb_get_intfdata(intf);161 struct huawei_cdc_ncm_state *drvstate = (void *)&usbnet_dev->data;162 bool callsub;163 struct cdc_ncm_ctx *ctx = drvstate->ctx;164 165 /* should we call subdriver's resume function? */166 callsub =167 (intf == ctx->control &&168 drvstate->subdriver &&169 drvstate->subdriver->resume);170 171 if (callsub)172 ret = drvstate->subdriver->resume(intf);173 if (ret < 0)174 goto err;175 ret = usbnet_resume(intf);176 if (ret < 0 && callsub)177 drvstate->subdriver->suspend(intf, PMSG_SUSPEND);178err:179 return ret;180}181 182static const struct driver_info huawei_cdc_ncm_info = {183 .description = "Huawei CDC NCM device",184 .flags = FLAG_NO_SETINT | FLAG_MULTI_PACKET | FLAG_WWAN,185 .bind = huawei_cdc_ncm_bind,186 .unbind = huawei_cdc_ncm_unbind,187 .manage_power = huawei_cdc_ncm_manage_power,188 .rx_fixup = cdc_ncm_rx_fixup,189 .tx_fixup = cdc_ncm_tx_fixup,190};191 192static const struct usb_device_id huawei_cdc_ncm_devs[] = {193 /* Huawei NCM devices disguised as vendor specific */194 { USB_VENDOR_AND_INTERFACE_INFO(0x12d1, 0xff, 0x02, 0x16),195 .driver_info = (unsigned long)&huawei_cdc_ncm_info,196 },197 { USB_VENDOR_AND_INTERFACE_INFO(0x12d1, 0xff, 0x02, 0x46),198 .driver_info = (unsigned long)&huawei_cdc_ncm_info,199 },200 { USB_VENDOR_AND_INTERFACE_INFO(0x12d1, 0xff, 0x02, 0x76),201 .driver_info = (unsigned long)&huawei_cdc_ncm_info,202 },203 { USB_VENDOR_AND_INTERFACE_INFO(0x12d1, 0xff, 0x03, 0x16),204 .driver_info = (unsigned long)&huawei_cdc_ncm_info,205 },206 207 /* Terminating entry */208 {209 },210};211MODULE_DEVICE_TABLE(usb, huawei_cdc_ncm_devs);212 213static struct usb_driver huawei_cdc_ncm_driver = {214 .name = "huawei_cdc_ncm",215 .id_table = huawei_cdc_ncm_devs,216 .probe = usbnet_probe,217 .disconnect = usbnet_disconnect,218 .suspend = huawei_cdc_ncm_suspend,219 .resume = huawei_cdc_ncm_resume,220 .reset_resume = huawei_cdc_ncm_resume,221 .supports_autosuspend = 1,222 .disable_hub_initiated_lpm = 1,223};224module_usb_driver(huawei_cdc_ncm_driver);225MODULE_AUTHOR("Enrico Mioso <mrkiko.rs@gmail.com>");226MODULE_DESCRIPTION("USB CDC NCM host driver with encapsulated protocol support");227MODULE_LICENSE("GPL");228