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