18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * phonet.c -- USB CDC Phonet host driver
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2008-2009 Nokia Corporation. All rights reserved.
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * Author: Rémi Denis-Courmont
88c2ecf20Sopenharmony_ci */
98c2ecf20Sopenharmony_ci
108c2ecf20Sopenharmony_ci#include <linux/kernel.h>
118c2ecf20Sopenharmony_ci#include <linux/mm.h>
128c2ecf20Sopenharmony_ci#include <linux/module.h>
138c2ecf20Sopenharmony_ci#include <linux/gfp.h>
148c2ecf20Sopenharmony_ci#include <linux/usb.h>
158c2ecf20Sopenharmony_ci#include <linux/usb/cdc.h>
168c2ecf20Sopenharmony_ci#include <linux/netdevice.h>
178c2ecf20Sopenharmony_ci#include <linux/if_arp.h>
188c2ecf20Sopenharmony_ci#include <linux/if_phonet.h>
198c2ecf20Sopenharmony_ci#include <linux/phonet.h>
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_ci#define PN_MEDIA_USB	0x1B
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_cistatic const unsigned rxq_size = 17;
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_cistruct usbpn_dev {
268c2ecf20Sopenharmony_ci	struct net_device	*dev;
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_ci	struct usb_interface	*intf, *data_intf;
298c2ecf20Sopenharmony_ci	struct usb_device	*usb;
308c2ecf20Sopenharmony_ci	unsigned int		tx_pipe, rx_pipe;
318c2ecf20Sopenharmony_ci	u8 active_setting;
328c2ecf20Sopenharmony_ci	u8 disconnected;
338c2ecf20Sopenharmony_ci
348c2ecf20Sopenharmony_ci	unsigned		tx_queue;
358c2ecf20Sopenharmony_ci	spinlock_t		tx_lock;
368c2ecf20Sopenharmony_ci
378c2ecf20Sopenharmony_ci	spinlock_t		rx_lock;
388c2ecf20Sopenharmony_ci	struct sk_buff		*rx_skb;
398c2ecf20Sopenharmony_ci	struct urb		*urbs[];
408c2ecf20Sopenharmony_ci};
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_cistatic void tx_complete(struct urb *req);
438c2ecf20Sopenharmony_cistatic void rx_complete(struct urb *req);
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_ci/*
468c2ecf20Sopenharmony_ci * Network device callbacks
478c2ecf20Sopenharmony_ci */
488c2ecf20Sopenharmony_cistatic netdev_tx_t usbpn_xmit(struct sk_buff *skb, struct net_device *dev)
498c2ecf20Sopenharmony_ci{
508c2ecf20Sopenharmony_ci	struct usbpn_dev *pnd = netdev_priv(dev);
518c2ecf20Sopenharmony_ci	struct urb *req = NULL;
528c2ecf20Sopenharmony_ci	unsigned long flags;
538c2ecf20Sopenharmony_ci	int err;
548c2ecf20Sopenharmony_ci
558c2ecf20Sopenharmony_ci	if (skb->protocol != htons(ETH_P_PHONET))
568c2ecf20Sopenharmony_ci		goto drop;
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_ci	req = usb_alloc_urb(0, GFP_ATOMIC);
598c2ecf20Sopenharmony_ci	if (!req)
608c2ecf20Sopenharmony_ci		goto drop;
618c2ecf20Sopenharmony_ci	usb_fill_bulk_urb(req, pnd->usb, pnd->tx_pipe, skb->data, skb->len,
628c2ecf20Sopenharmony_ci				tx_complete, skb);
638c2ecf20Sopenharmony_ci	req->transfer_flags = URB_ZERO_PACKET;
648c2ecf20Sopenharmony_ci	err = usb_submit_urb(req, GFP_ATOMIC);
658c2ecf20Sopenharmony_ci	if (err) {
668c2ecf20Sopenharmony_ci		usb_free_urb(req);
678c2ecf20Sopenharmony_ci		goto drop;
688c2ecf20Sopenharmony_ci	}
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_ci	spin_lock_irqsave(&pnd->tx_lock, flags);
718c2ecf20Sopenharmony_ci	pnd->tx_queue++;
728c2ecf20Sopenharmony_ci	if (pnd->tx_queue >= dev->tx_queue_len)
738c2ecf20Sopenharmony_ci		netif_stop_queue(dev);
748c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&pnd->tx_lock, flags);
758c2ecf20Sopenharmony_ci	return NETDEV_TX_OK;
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_cidrop:
788c2ecf20Sopenharmony_ci	dev_kfree_skb(skb);
798c2ecf20Sopenharmony_ci	dev->stats.tx_dropped++;
808c2ecf20Sopenharmony_ci	return NETDEV_TX_OK;
818c2ecf20Sopenharmony_ci}
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_cistatic void tx_complete(struct urb *req)
848c2ecf20Sopenharmony_ci{
858c2ecf20Sopenharmony_ci	struct sk_buff *skb = req->context;
868c2ecf20Sopenharmony_ci	struct net_device *dev = skb->dev;
878c2ecf20Sopenharmony_ci	struct usbpn_dev *pnd = netdev_priv(dev);
888c2ecf20Sopenharmony_ci	int status = req->status;
898c2ecf20Sopenharmony_ci	unsigned long flags;
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_ci	switch (status) {
928c2ecf20Sopenharmony_ci	case 0:
938c2ecf20Sopenharmony_ci		dev->stats.tx_bytes += skb->len;
948c2ecf20Sopenharmony_ci		break;
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_ci	case -ENOENT:
978c2ecf20Sopenharmony_ci	case -ECONNRESET:
988c2ecf20Sopenharmony_ci	case -ESHUTDOWN:
998c2ecf20Sopenharmony_ci		dev->stats.tx_aborted_errors++;
1008c2ecf20Sopenharmony_ci		fallthrough;
1018c2ecf20Sopenharmony_ci	default:
1028c2ecf20Sopenharmony_ci		dev->stats.tx_errors++;
1038c2ecf20Sopenharmony_ci		dev_dbg(&dev->dev, "TX error (%d)\n", status);
1048c2ecf20Sopenharmony_ci	}
1058c2ecf20Sopenharmony_ci	dev->stats.tx_packets++;
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci	spin_lock_irqsave(&pnd->tx_lock, flags);
1088c2ecf20Sopenharmony_ci	pnd->tx_queue--;
1098c2ecf20Sopenharmony_ci	netif_wake_queue(dev);
1108c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&pnd->tx_lock, flags);
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_ci	dev_kfree_skb_any(skb);
1138c2ecf20Sopenharmony_ci	usb_free_urb(req);
1148c2ecf20Sopenharmony_ci}
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_cistatic int rx_submit(struct usbpn_dev *pnd, struct urb *req, gfp_t gfp_flags)
1178c2ecf20Sopenharmony_ci{
1188c2ecf20Sopenharmony_ci	struct net_device *dev = pnd->dev;
1198c2ecf20Sopenharmony_ci	struct page *page;
1208c2ecf20Sopenharmony_ci	int err;
1218c2ecf20Sopenharmony_ci
1228c2ecf20Sopenharmony_ci	page = __dev_alloc_page(gfp_flags | __GFP_NOMEMALLOC);
1238c2ecf20Sopenharmony_ci	if (!page)
1248c2ecf20Sopenharmony_ci		return -ENOMEM;
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_ci	usb_fill_bulk_urb(req, pnd->usb, pnd->rx_pipe, page_address(page),
1278c2ecf20Sopenharmony_ci				PAGE_SIZE, rx_complete, dev);
1288c2ecf20Sopenharmony_ci	req->transfer_flags = 0;
1298c2ecf20Sopenharmony_ci	err = usb_submit_urb(req, gfp_flags);
1308c2ecf20Sopenharmony_ci	if (unlikely(err)) {
1318c2ecf20Sopenharmony_ci		dev_dbg(&dev->dev, "RX submit error (%d)\n", err);
1328c2ecf20Sopenharmony_ci		put_page(page);
1338c2ecf20Sopenharmony_ci	}
1348c2ecf20Sopenharmony_ci	return err;
1358c2ecf20Sopenharmony_ci}
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_cistatic void rx_complete(struct urb *req)
1388c2ecf20Sopenharmony_ci{
1398c2ecf20Sopenharmony_ci	struct net_device *dev = req->context;
1408c2ecf20Sopenharmony_ci	struct usbpn_dev *pnd = netdev_priv(dev);
1418c2ecf20Sopenharmony_ci	struct page *page = virt_to_page(req->transfer_buffer);
1428c2ecf20Sopenharmony_ci	struct sk_buff *skb;
1438c2ecf20Sopenharmony_ci	unsigned long flags;
1448c2ecf20Sopenharmony_ci	int status = req->status;
1458c2ecf20Sopenharmony_ci
1468c2ecf20Sopenharmony_ci	switch (status) {
1478c2ecf20Sopenharmony_ci	case 0:
1488c2ecf20Sopenharmony_ci		spin_lock_irqsave(&pnd->rx_lock, flags);
1498c2ecf20Sopenharmony_ci		skb = pnd->rx_skb;
1508c2ecf20Sopenharmony_ci		if (!skb) {
1518c2ecf20Sopenharmony_ci			skb = pnd->rx_skb = netdev_alloc_skb(dev, 12);
1528c2ecf20Sopenharmony_ci			if (likely(skb)) {
1538c2ecf20Sopenharmony_ci				/* Can't use pskb_pull() on page in IRQ */
1548c2ecf20Sopenharmony_ci				skb_put_data(skb, page_address(page), 1);
1558c2ecf20Sopenharmony_ci				skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags,
1568c2ecf20Sopenharmony_ci						page, 1, req->actual_length,
1578c2ecf20Sopenharmony_ci						PAGE_SIZE);
1588c2ecf20Sopenharmony_ci				page = NULL;
1598c2ecf20Sopenharmony_ci			}
1608c2ecf20Sopenharmony_ci		} else {
1618c2ecf20Sopenharmony_ci			skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags,
1628c2ecf20Sopenharmony_ci					page, 0, req->actual_length,
1638c2ecf20Sopenharmony_ci					PAGE_SIZE);
1648c2ecf20Sopenharmony_ci			page = NULL;
1658c2ecf20Sopenharmony_ci		}
1668c2ecf20Sopenharmony_ci		if (req->actual_length < PAGE_SIZE)
1678c2ecf20Sopenharmony_ci			pnd->rx_skb = NULL; /* Last fragment */
1688c2ecf20Sopenharmony_ci		else
1698c2ecf20Sopenharmony_ci			skb = NULL;
1708c2ecf20Sopenharmony_ci		spin_unlock_irqrestore(&pnd->rx_lock, flags);
1718c2ecf20Sopenharmony_ci		if (skb) {
1728c2ecf20Sopenharmony_ci			skb->protocol = htons(ETH_P_PHONET);
1738c2ecf20Sopenharmony_ci			skb_reset_mac_header(skb);
1748c2ecf20Sopenharmony_ci			__skb_pull(skb, 1);
1758c2ecf20Sopenharmony_ci			skb->dev = dev;
1768c2ecf20Sopenharmony_ci			dev->stats.rx_packets++;
1778c2ecf20Sopenharmony_ci			dev->stats.rx_bytes += skb->len;
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_ci			netif_rx(skb);
1808c2ecf20Sopenharmony_ci		}
1818c2ecf20Sopenharmony_ci		goto resubmit;
1828c2ecf20Sopenharmony_ci
1838c2ecf20Sopenharmony_ci	case -ENOENT:
1848c2ecf20Sopenharmony_ci	case -ECONNRESET:
1858c2ecf20Sopenharmony_ci	case -ESHUTDOWN:
1868c2ecf20Sopenharmony_ci		req = NULL;
1878c2ecf20Sopenharmony_ci		break;
1888c2ecf20Sopenharmony_ci
1898c2ecf20Sopenharmony_ci	case -EOVERFLOW:
1908c2ecf20Sopenharmony_ci		dev->stats.rx_over_errors++;
1918c2ecf20Sopenharmony_ci		dev_dbg(&dev->dev, "RX overflow\n");
1928c2ecf20Sopenharmony_ci		break;
1938c2ecf20Sopenharmony_ci
1948c2ecf20Sopenharmony_ci	case -EILSEQ:
1958c2ecf20Sopenharmony_ci		dev->stats.rx_crc_errors++;
1968c2ecf20Sopenharmony_ci		break;
1978c2ecf20Sopenharmony_ci	}
1988c2ecf20Sopenharmony_ci
1998c2ecf20Sopenharmony_ci	dev->stats.rx_errors++;
2008c2ecf20Sopenharmony_ciresubmit:
2018c2ecf20Sopenharmony_ci	if (page)
2028c2ecf20Sopenharmony_ci		put_page(page);
2038c2ecf20Sopenharmony_ci	if (req)
2048c2ecf20Sopenharmony_ci		rx_submit(pnd, req, GFP_ATOMIC);
2058c2ecf20Sopenharmony_ci}
2068c2ecf20Sopenharmony_ci
2078c2ecf20Sopenharmony_cistatic int usbpn_close(struct net_device *dev);
2088c2ecf20Sopenharmony_ci
2098c2ecf20Sopenharmony_cistatic int usbpn_open(struct net_device *dev)
2108c2ecf20Sopenharmony_ci{
2118c2ecf20Sopenharmony_ci	struct usbpn_dev *pnd = netdev_priv(dev);
2128c2ecf20Sopenharmony_ci	int err;
2138c2ecf20Sopenharmony_ci	unsigned i;
2148c2ecf20Sopenharmony_ci	unsigned num = pnd->data_intf->cur_altsetting->desc.bInterfaceNumber;
2158c2ecf20Sopenharmony_ci
2168c2ecf20Sopenharmony_ci	err = usb_set_interface(pnd->usb, num, pnd->active_setting);
2178c2ecf20Sopenharmony_ci	if (err)
2188c2ecf20Sopenharmony_ci		return err;
2198c2ecf20Sopenharmony_ci
2208c2ecf20Sopenharmony_ci	for (i = 0; i < rxq_size; i++) {
2218c2ecf20Sopenharmony_ci		struct urb *req = usb_alloc_urb(0, GFP_KERNEL);
2228c2ecf20Sopenharmony_ci
2238c2ecf20Sopenharmony_ci		if (!req || rx_submit(pnd, req, GFP_KERNEL)) {
2248c2ecf20Sopenharmony_ci			usb_free_urb(req);
2258c2ecf20Sopenharmony_ci			usbpn_close(dev);
2268c2ecf20Sopenharmony_ci			return -ENOMEM;
2278c2ecf20Sopenharmony_ci		}
2288c2ecf20Sopenharmony_ci		pnd->urbs[i] = req;
2298c2ecf20Sopenharmony_ci	}
2308c2ecf20Sopenharmony_ci
2318c2ecf20Sopenharmony_ci	netif_wake_queue(dev);
2328c2ecf20Sopenharmony_ci	return 0;
2338c2ecf20Sopenharmony_ci}
2348c2ecf20Sopenharmony_ci
2358c2ecf20Sopenharmony_cistatic int usbpn_close(struct net_device *dev)
2368c2ecf20Sopenharmony_ci{
2378c2ecf20Sopenharmony_ci	struct usbpn_dev *pnd = netdev_priv(dev);
2388c2ecf20Sopenharmony_ci	unsigned i;
2398c2ecf20Sopenharmony_ci	unsigned num = pnd->data_intf->cur_altsetting->desc.bInterfaceNumber;
2408c2ecf20Sopenharmony_ci
2418c2ecf20Sopenharmony_ci	netif_stop_queue(dev);
2428c2ecf20Sopenharmony_ci
2438c2ecf20Sopenharmony_ci	for (i = 0; i < rxq_size; i++) {
2448c2ecf20Sopenharmony_ci		struct urb *req = pnd->urbs[i];
2458c2ecf20Sopenharmony_ci
2468c2ecf20Sopenharmony_ci		if (!req)
2478c2ecf20Sopenharmony_ci			continue;
2488c2ecf20Sopenharmony_ci		usb_kill_urb(req);
2498c2ecf20Sopenharmony_ci		usb_free_urb(req);
2508c2ecf20Sopenharmony_ci		pnd->urbs[i] = NULL;
2518c2ecf20Sopenharmony_ci	}
2528c2ecf20Sopenharmony_ci
2538c2ecf20Sopenharmony_ci	return usb_set_interface(pnd->usb, num, !pnd->active_setting);
2548c2ecf20Sopenharmony_ci}
2558c2ecf20Sopenharmony_ci
2568c2ecf20Sopenharmony_cistatic int usbpn_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
2578c2ecf20Sopenharmony_ci{
2588c2ecf20Sopenharmony_ci	struct if_phonet_req *req = (struct if_phonet_req *)ifr;
2598c2ecf20Sopenharmony_ci
2608c2ecf20Sopenharmony_ci	switch (cmd) {
2618c2ecf20Sopenharmony_ci	case SIOCPNGAUTOCONF:
2628c2ecf20Sopenharmony_ci		req->ifr_phonet_autoconf.device = PN_DEV_PC;
2638c2ecf20Sopenharmony_ci		return 0;
2648c2ecf20Sopenharmony_ci	}
2658c2ecf20Sopenharmony_ci	return -ENOIOCTLCMD;
2668c2ecf20Sopenharmony_ci}
2678c2ecf20Sopenharmony_ci
2688c2ecf20Sopenharmony_cistatic const struct net_device_ops usbpn_ops = {
2698c2ecf20Sopenharmony_ci	.ndo_open	= usbpn_open,
2708c2ecf20Sopenharmony_ci	.ndo_stop	= usbpn_close,
2718c2ecf20Sopenharmony_ci	.ndo_start_xmit = usbpn_xmit,
2728c2ecf20Sopenharmony_ci	.ndo_do_ioctl	= usbpn_ioctl,
2738c2ecf20Sopenharmony_ci};
2748c2ecf20Sopenharmony_ci
2758c2ecf20Sopenharmony_cistatic void usbpn_setup(struct net_device *dev)
2768c2ecf20Sopenharmony_ci{
2778c2ecf20Sopenharmony_ci	dev->features		= 0;
2788c2ecf20Sopenharmony_ci	dev->netdev_ops		= &usbpn_ops,
2798c2ecf20Sopenharmony_ci	dev->header_ops		= &phonet_header_ops;
2808c2ecf20Sopenharmony_ci	dev->type		= ARPHRD_PHONET;
2818c2ecf20Sopenharmony_ci	dev->flags		= IFF_POINTOPOINT | IFF_NOARP;
2828c2ecf20Sopenharmony_ci	dev->mtu		= PHONET_MAX_MTU;
2838c2ecf20Sopenharmony_ci	dev->min_mtu		= PHONET_MIN_MTU;
2848c2ecf20Sopenharmony_ci	dev->max_mtu		= PHONET_MAX_MTU;
2858c2ecf20Sopenharmony_ci	dev->hard_header_len	= 1;
2868c2ecf20Sopenharmony_ci	dev->dev_addr[0]	= PN_MEDIA_USB;
2878c2ecf20Sopenharmony_ci	dev->addr_len		= 1;
2888c2ecf20Sopenharmony_ci	dev->tx_queue_len	= 3;
2898c2ecf20Sopenharmony_ci
2908c2ecf20Sopenharmony_ci	dev->needs_free_netdev	= true;
2918c2ecf20Sopenharmony_ci}
2928c2ecf20Sopenharmony_ci
2938c2ecf20Sopenharmony_ci/*
2948c2ecf20Sopenharmony_ci * USB driver callbacks
2958c2ecf20Sopenharmony_ci */
2968c2ecf20Sopenharmony_cistatic const struct usb_device_id usbpn_ids[] = {
2978c2ecf20Sopenharmony_ci	{
2988c2ecf20Sopenharmony_ci		.match_flags = USB_DEVICE_ID_MATCH_VENDOR
2998c2ecf20Sopenharmony_ci			| USB_DEVICE_ID_MATCH_INT_CLASS
3008c2ecf20Sopenharmony_ci			| USB_DEVICE_ID_MATCH_INT_SUBCLASS,
3018c2ecf20Sopenharmony_ci		.idVendor = 0x0421, /* Nokia */
3028c2ecf20Sopenharmony_ci		.bInterfaceClass = USB_CLASS_COMM,
3038c2ecf20Sopenharmony_ci		.bInterfaceSubClass = 0xFE,
3048c2ecf20Sopenharmony_ci	},
3058c2ecf20Sopenharmony_ci	{ },
3068c2ecf20Sopenharmony_ci};
3078c2ecf20Sopenharmony_ci
3088c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(usb, usbpn_ids);
3098c2ecf20Sopenharmony_ci
3108c2ecf20Sopenharmony_cistatic struct usb_driver usbpn_driver;
3118c2ecf20Sopenharmony_ci
3128c2ecf20Sopenharmony_cistatic int usbpn_probe(struct usb_interface *intf, const struct usb_device_id *id)
3138c2ecf20Sopenharmony_ci{
3148c2ecf20Sopenharmony_ci	static const char ifname[] = "usbpn%d";
3158c2ecf20Sopenharmony_ci	const struct usb_cdc_union_desc *union_header = NULL;
3168c2ecf20Sopenharmony_ci	const struct usb_host_interface *data_desc;
3178c2ecf20Sopenharmony_ci	struct usb_interface *data_intf;
3188c2ecf20Sopenharmony_ci	struct usb_device *usbdev = interface_to_usbdev(intf);
3198c2ecf20Sopenharmony_ci	struct net_device *dev;
3208c2ecf20Sopenharmony_ci	struct usbpn_dev *pnd;
3218c2ecf20Sopenharmony_ci	u8 *data;
3228c2ecf20Sopenharmony_ci	int phonet = 0;
3238c2ecf20Sopenharmony_ci	int len, err;
3248c2ecf20Sopenharmony_ci	struct usb_cdc_parsed_header hdr;
3258c2ecf20Sopenharmony_ci
3268c2ecf20Sopenharmony_ci	data = intf->altsetting->extra;
3278c2ecf20Sopenharmony_ci	len = intf->altsetting->extralen;
3288c2ecf20Sopenharmony_ci	cdc_parse_cdc_header(&hdr, intf, data, len);
3298c2ecf20Sopenharmony_ci	union_header = hdr.usb_cdc_union_desc;
3308c2ecf20Sopenharmony_ci	phonet = hdr.phonet_magic_present;
3318c2ecf20Sopenharmony_ci
3328c2ecf20Sopenharmony_ci	if (!union_header || !phonet)
3338c2ecf20Sopenharmony_ci		return -EINVAL;
3348c2ecf20Sopenharmony_ci
3358c2ecf20Sopenharmony_ci	data_intf = usb_ifnum_to_if(usbdev, union_header->bSlaveInterface0);
3368c2ecf20Sopenharmony_ci	if (data_intf == NULL)
3378c2ecf20Sopenharmony_ci		return -ENODEV;
3388c2ecf20Sopenharmony_ci	/* Data interface has one inactive and one active setting */
3398c2ecf20Sopenharmony_ci	if (data_intf->num_altsetting != 2)
3408c2ecf20Sopenharmony_ci		return -EINVAL;
3418c2ecf20Sopenharmony_ci	if (data_intf->altsetting[0].desc.bNumEndpoints == 0 &&
3428c2ecf20Sopenharmony_ci	    data_intf->altsetting[1].desc.bNumEndpoints == 2)
3438c2ecf20Sopenharmony_ci		data_desc = data_intf->altsetting + 1;
3448c2ecf20Sopenharmony_ci	else
3458c2ecf20Sopenharmony_ci	if (data_intf->altsetting[0].desc.bNumEndpoints == 2 &&
3468c2ecf20Sopenharmony_ci	    data_intf->altsetting[1].desc.bNumEndpoints == 0)
3478c2ecf20Sopenharmony_ci		data_desc = data_intf->altsetting;
3488c2ecf20Sopenharmony_ci	else
3498c2ecf20Sopenharmony_ci		return -EINVAL;
3508c2ecf20Sopenharmony_ci
3518c2ecf20Sopenharmony_ci	dev = alloc_netdev(struct_size(pnd, urbs, rxq_size), ifname,
3528c2ecf20Sopenharmony_ci			   NET_NAME_UNKNOWN, usbpn_setup);
3538c2ecf20Sopenharmony_ci	if (!dev)
3548c2ecf20Sopenharmony_ci		return -ENOMEM;
3558c2ecf20Sopenharmony_ci
3568c2ecf20Sopenharmony_ci	pnd = netdev_priv(dev);
3578c2ecf20Sopenharmony_ci	SET_NETDEV_DEV(dev, &intf->dev);
3588c2ecf20Sopenharmony_ci
3598c2ecf20Sopenharmony_ci	pnd->dev = dev;
3608c2ecf20Sopenharmony_ci	pnd->usb = usbdev;
3618c2ecf20Sopenharmony_ci	pnd->intf = intf;
3628c2ecf20Sopenharmony_ci	pnd->data_intf = data_intf;
3638c2ecf20Sopenharmony_ci	spin_lock_init(&pnd->tx_lock);
3648c2ecf20Sopenharmony_ci	spin_lock_init(&pnd->rx_lock);
3658c2ecf20Sopenharmony_ci	/* Endpoints */
3668c2ecf20Sopenharmony_ci	if (usb_pipein(data_desc->endpoint[0].desc.bEndpointAddress)) {
3678c2ecf20Sopenharmony_ci		pnd->rx_pipe = usb_rcvbulkpipe(usbdev,
3688c2ecf20Sopenharmony_ci			data_desc->endpoint[0].desc.bEndpointAddress);
3698c2ecf20Sopenharmony_ci		pnd->tx_pipe = usb_sndbulkpipe(usbdev,
3708c2ecf20Sopenharmony_ci			data_desc->endpoint[1].desc.bEndpointAddress);
3718c2ecf20Sopenharmony_ci	} else {
3728c2ecf20Sopenharmony_ci		pnd->rx_pipe = usb_rcvbulkpipe(usbdev,
3738c2ecf20Sopenharmony_ci			data_desc->endpoint[1].desc.bEndpointAddress);
3748c2ecf20Sopenharmony_ci		pnd->tx_pipe = usb_sndbulkpipe(usbdev,
3758c2ecf20Sopenharmony_ci			data_desc->endpoint[0].desc.bEndpointAddress);
3768c2ecf20Sopenharmony_ci	}
3778c2ecf20Sopenharmony_ci	pnd->active_setting = data_desc - data_intf->altsetting;
3788c2ecf20Sopenharmony_ci
3798c2ecf20Sopenharmony_ci	err = usb_driver_claim_interface(&usbpn_driver, data_intf, pnd);
3808c2ecf20Sopenharmony_ci	if (err)
3818c2ecf20Sopenharmony_ci		goto out;
3828c2ecf20Sopenharmony_ci
3838c2ecf20Sopenharmony_ci	/* Force inactive mode until the network device is brought UP */
3848c2ecf20Sopenharmony_ci	usb_set_interface(usbdev, union_header->bSlaveInterface0,
3858c2ecf20Sopenharmony_ci				!pnd->active_setting);
3868c2ecf20Sopenharmony_ci	usb_set_intfdata(intf, pnd);
3878c2ecf20Sopenharmony_ci
3888c2ecf20Sopenharmony_ci	err = register_netdev(dev);
3898c2ecf20Sopenharmony_ci	if (err) {
3908c2ecf20Sopenharmony_ci		/* Set disconnected flag so that disconnect() returns early. */
3918c2ecf20Sopenharmony_ci		pnd->disconnected = 1;
3928c2ecf20Sopenharmony_ci		usb_driver_release_interface(&usbpn_driver, data_intf);
3938c2ecf20Sopenharmony_ci		goto out;
3948c2ecf20Sopenharmony_ci	}
3958c2ecf20Sopenharmony_ci
3968c2ecf20Sopenharmony_ci	dev_dbg(&dev->dev, "USB CDC Phonet device found\n");
3978c2ecf20Sopenharmony_ci	return 0;
3988c2ecf20Sopenharmony_ci
3998c2ecf20Sopenharmony_ciout:
4008c2ecf20Sopenharmony_ci	usb_set_intfdata(intf, NULL);
4018c2ecf20Sopenharmony_ci	free_netdev(dev);
4028c2ecf20Sopenharmony_ci	return err;
4038c2ecf20Sopenharmony_ci}
4048c2ecf20Sopenharmony_ci
4058c2ecf20Sopenharmony_cistatic void usbpn_disconnect(struct usb_interface *intf)
4068c2ecf20Sopenharmony_ci{
4078c2ecf20Sopenharmony_ci	struct usbpn_dev *pnd = usb_get_intfdata(intf);
4088c2ecf20Sopenharmony_ci
4098c2ecf20Sopenharmony_ci	if (pnd->disconnected)
4108c2ecf20Sopenharmony_ci		return;
4118c2ecf20Sopenharmony_ci
4128c2ecf20Sopenharmony_ci	pnd->disconnected = 1;
4138c2ecf20Sopenharmony_ci	usb_driver_release_interface(&usbpn_driver,
4148c2ecf20Sopenharmony_ci			(pnd->intf == intf) ? pnd->data_intf : pnd->intf);
4158c2ecf20Sopenharmony_ci	unregister_netdev(pnd->dev);
4168c2ecf20Sopenharmony_ci}
4178c2ecf20Sopenharmony_ci
4188c2ecf20Sopenharmony_cistatic struct usb_driver usbpn_driver = {
4198c2ecf20Sopenharmony_ci	.name =		"cdc_phonet",
4208c2ecf20Sopenharmony_ci	.probe =	usbpn_probe,
4218c2ecf20Sopenharmony_ci	.disconnect =	usbpn_disconnect,
4228c2ecf20Sopenharmony_ci	.id_table =	usbpn_ids,
4238c2ecf20Sopenharmony_ci	.disable_hub_initiated_lpm = 1,
4248c2ecf20Sopenharmony_ci};
4258c2ecf20Sopenharmony_ci
4268c2ecf20Sopenharmony_cimodule_usb_driver(usbpn_driver);
4278c2ecf20Sopenharmony_ci
4288c2ecf20Sopenharmony_ciMODULE_AUTHOR("Remi Denis-Courmont");
4298c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("USB CDC Phonet host interface");
4308c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
431