xref: /openbmc/linux/drivers/net/usb/cdc-phonet.c (revision 03ab8e6297acd1bc0eedaa050e2a1635c576fd11)
12b27bdccSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
287cf6560SRémi Denis-Courmont /*
387cf6560SRémi Denis-Courmont  * phonet.c -- USB CDC Phonet host driver
487cf6560SRémi Denis-Courmont  *
587cf6560SRémi Denis-Courmont  * Copyright (C) 2008-2009 Nokia Corporation. All rights reserved.
687cf6560SRémi Denis-Courmont  *
787cf6560SRémi Denis-Courmont  * Author: Rémi Denis-Courmont
887cf6560SRémi Denis-Courmont  */
987cf6560SRémi Denis-Courmont 
1087cf6560SRémi Denis-Courmont #include <linux/kernel.h>
11b7f080cfSAlexey Dobriyan #include <linux/mm.h>
1287cf6560SRémi Denis-Courmont #include <linux/module.h>
135a0e3ad6STejun Heo #include <linux/gfp.h>
1487cf6560SRémi Denis-Courmont #include <linux/usb.h>
1587cf6560SRémi Denis-Courmont #include <linux/usb/cdc.h>
1687cf6560SRémi Denis-Courmont #include <linux/netdevice.h>
1787cf6560SRémi Denis-Courmont #include <linux/if_arp.h>
1887cf6560SRémi Denis-Courmont #include <linux/if_phonet.h>
1902571f89SRémi Denis-Courmont #include <linux/phonet.h>
2087cf6560SRémi Denis-Courmont 
2187cf6560SRémi Denis-Courmont #define PN_MEDIA_USB	0x1B
2287cf6560SRémi Denis-Courmont 
2387cf6560SRémi Denis-Courmont static const unsigned rxq_size = 17;
2487cf6560SRémi Denis-Courmont 
2587cf6560SRémi Denis-Courmont struct usbpn_dev {
2687cf6560SRémi Denis-Courmont 	struct net_device	*dev;
2787cf6560SRémi Denis-Courmont 
2887cf6560SRémi Denis-Courmont 	struct usb_interface	*intf, *data_intf;
2987cf6560SRémi Denis-Courmont 	struct usb_device	*usb;
3087cf6560SRémi Denis-Courmont 	unsigned int		tx_pipe, rx_pipe;
3187cf6560SRémi Denis-Courmont 	u8 active_setting;
3287cf6560SRémi Denis-Courmont 	u8 disconnected;
3387cf6560SRémi Denis-Courmont 
3487cf6560SRémi Denis-Courmont 	unsigned		tx_queue;
3587cf6560SRémi Denis-Courmont 	spinlock_t		tx_lock;
3687cf6560SRémi Denis-Courmont 
3787cf6560SRémi Denis-Courmont 	spinlock_t		rx_lock;
3887cf6560SRémi Denis-Courmont 	struct sk_buff		*rx_skb;
39dc3cc347SGustavo A. R. Silva 	struct urb		*urbs[];
4087cf6560SRémi Denis-Courmont };
4187cf6560SRémi Denis-Courmont 
4287cf6560SRémi Denis-Courmont static void tx_complete(struct urb *req);
4387cf6560SRémi Denis-Courmont static void rx_complete(struct urb *req);
4487cf6560SRémi Denis-Courmont 
4587cf6560SRémi Denis-Courmont /*
4687cf6560SRémi Denis-Courmont  * Network device callbacks
4787cf6560SRémi Denis-Courmont  */
usbpn_xmit(struct sk_buff * skb,struct net_device * dev)4825a79c41SStephen Hemminger static netdev_tx_t usbpn_xmit(struct sk_buff *skb, struct net_device *dev)
4987cf6560SRémi Denis-Courmont {
5087cf6560SRémi Denis-Courmont 	struct usbpn_dev *pnd = netdev_priv(dev);
5187cf6560SRémi Denis-Courmont 	struct urb *req = NULL;
5287cf6560SRémi Denis-Courmont 	unsigned long flags;
5387cf6560SRémi Denis-Courmont 	int err;
5487cf6560SRémi Denis-Courmont 
5587cf6560SRémi Denis-Courmont 	if (skb->protocol != htons(ETH_P_PHONET))
5687cf6560SRémi Denis-Courmont 		goto drop;
5787cf6560SRémi Denis-Courmont 
5887cf6560SRémi Denis-Courmont 	req = usb_alloc_urb(0, GFP_ATOMIC);
5987cf6560SRémi Denis-Courmont 	if (!req)
6087cf6560SRémi Denis-Courmont 		goto drop;
6187cf6560SRémi Denis-Courmont 	usb_fill_bulk_urb(req, pnd->usb, pnd->tx_pipe, skb->data, skb->len,
6287cf6560SRémi Denis-Courmont 				tx_complete, skb);
6387cf6560SRémi Denis-Courmont 	req->transfer_flags = URB_ZERO_PACKET;
6487cf6560SRémi Denis-Courmont 	err = usb_submit_urb(req, GFP_ATOMIC);
6587cf6560SRémi Denis-Courmont 	if (err) {
6687cf6560SRémi Denis-Courmont 		usb_free_urb(req);
6787cf6560SRémi Denis-Courmont 		goto drop;
6887cf6560SRémi Denis-Courmont 	}
6987cf6560SRémi Denis-Courmont 
7087cf6560SRémi Denis-Courmont 	spin_lock_irqsave(&pnd->tx_lock, flags);
7187cf6560SRémi Denis-Courmont 	pnd->tx_queue++;
7287cf6560SRémi Denis-Courmont 	if (pnd->tx_queue >= dev->tx_queue_len)
7387cf6560SRémi Denis-Courmont 		netif_stop_queue(dev);
7487cf6560SRémi Denis-Courmont 	spin_unlock_irqrestore(&pnd->tx_lock, flags);
7525a79c41SStephen Hemminger 	return NETDEV_TX_OK;
7687cf6560SRémi Denis-Courmont 
7787cf6560SRémi Denis-Courmont drop:
7887cf6560SRémi Denis-Courmont 	dev_kfree_skb(skb);
7987cf6560SRémi Denis-Courmont 	dev->stats.tx_dropped++;
8025a79c41SStephen Hemminger 	return NETDEV_TX_OK;
8187cf6560SRémi Denis-Courmont }
8287cf6560SRémi Denis-Courmont 
tx_complete(struct urb * req)8387cf6560SRémi Denis-Courmont static void tx_complete(struct urb *req)
8487cf6560SRémi Denis-Courmont {
8587cf6560SRémi Denis-Courmont 	struct sk_buff *skb = req->context;
8687cf6560SRémi Denis-Courmont 	struct net_device *dev = skb->dev;
8787cf6560SRémi Denis-Courmont 	struct usbpn_dev *pnd = netdev_priv(dev);
88c31fd6c2SOliver Neukum 	int status = req->status;
89fafa6b10SSebastian Andrzej Siewior 	unsigned long flags;
9087cf6560SRémi Denis-Courmont 
91c31fd6c2SOliver Neukum 	switch (status) {
9287cf6560SRémi Denis-Courmont 	case 0:
9387cf6560SRémi Denis-Courmont 		dev->stats.tx_bytes += skb->len;
9487cf6560SRémi Denis-Courmont 		break;
9587cf6560SRémi Denis-Courmont 
9687cf6560SRémi Denis-Courmont 	case -ENOENT:
9787cf6560SRémi Denis-Courmont 	case -ECONNRESET:
9887cf6560SRémi Denis-Courmont 	case -ESHUTDOWN:
9987cf6560SRémi Denis-Courmont 		dev->stats.tx_aborted_errors++;
100df561f66SGustavo A. R. Silva 		fallthrough;
10187cf6560SRémi Denis-Courmont 	default:
10287cf6560SRémi Denis-Courmont 		dev->stats.tx_errors++;
103c31fd6c2SOliver Neukum 		dev_dbg(&dev->dev, "TX error (%d)\n", status);
10487cf6560SRémi Denis-Courmont 	}
10587cf6560SRémi Denis-Courmont 	dev->stats.tx_packets++;
10687cf6560SRémi Denis-Courmont 
107fafa6b10SSebastian Andrzej Siewior 	spin_lock_irqsave(&pnd->tx_lock, flags);
10887cf6560SRémi Denis-Courmont 	pnd->tx_queue--;
10987cf6560SRémi Denis-Courmont 	netif_wake_queue(dev);
110fafa6b10SSebastian Andrzej Siewior 	spin_unlock_irqrestore(&pnd->tx_lock, flags);
11187cf6560SRémi Denis-Courmont 
11287cf6560SRémi Denis-Courmont 	dev_kfree_skb_any(skb);
11387cf6560SRémi Denis-Courmont 	usb_free_urb(req);
11487cf6560SRémi Denis-Courmont }
11587cf6560SRémi Denis-Courmont 
rx_submit(struct usbpn_dev * pnd,struct urb * req,gfp_t gfp_flags)11687cf6560SRémi Denis-Courmont static int rx_submit(struct usbpn_dev *pnd, struct urb *req, gfp_t gfp_flags)
11787cf6560SRémi Denis-Courmont {
11887cf6560SRémi Denis-Courmont 	struct net_device *dev = pnd->dev;
11987cf6560SRémi Denis-Courmont 	struct page *page;
12087cf6560SRémi Denis-Courmont 	int err;
12187cf6560SRémi Denis-Courmont 
1225693d284SAlexander Duyck 	page = __dev_alloc_page(gfp_flags | __GFP_NOMEMALLOC);
12387cf6560SRémi Denis-Courmont 	if (!page)
12487cf6560SRémi Denis-Courmont 		return -ENOMEM;
12587cf6560SRémi Denis-Courmont 
12687cf6560SRémi Denis-Courmont 	usb_fill_bulk_urb(req, pnd->usb, pnd->rx_pipe, page_address(page),
12787cf6560SRémi Denis-Courmont 				PAGE_SIZE, rx_complete, dev);
12887cf6560SRémi Denis-Courmont 	req->transfer_flags = 0;
12987cf6560SRémi Denis-Courmont 	err = usb_submit_urb(req, gfp_flags);
13087cf6560SRémi Denis-Courmont 	if (unlikely(err)) {
13187cf6560SRémi Denis-Courmont 		dev_dbg(&dev->dev, "RX submit error (%d)\n", err);
1321f2149c1SEric Dumazet 		put_page(page);
13387cf6560SRémi Denis-Courmont 	}
13487cf6560SRémi Denis-Courmont 	return err;
13587cf6560SRémi Denis-Courmont }
13687cf6560SRémi Denis-Courmont 
rx_complete(struct urb * req)13787cf6560SRémi Denis-Courmont static void rx_complete(struct urb *req)
13887cf6560SRémi Denis-Courmont {
13987cf6560SRémi Denis-Courmont 	struct net_device *dev = req->context;
14087cf6560SRémi Denis-Courmont 	struct usbpn_dev *pnd = netdev_priv(dev);
14187cf6560SRémi Denis-Courmont 	struct page *page = virt_to_page(req->transfer_buffer);
14287cf6560SRémi Denis-Courmont 	struct sk_buff *skb;
14387cf6560SRémi Denis-Courmont 	unsigned long flags;
144c31fd6c2SOliver Neukum 	int status = req->status;
14587cf6560SRémi Denis-Courmont 
146c31fd6c2SOliver Neukum 	switch (status) {
14787cf6560SRémi Denis-Courmont 	case 0:
14887cf6560SRémi Denis-Courmont 		spin_lock_irqsave(&pnd->rx_lock, flags);
14987cf6560SRémi Denis-Courmont 		skb = pnd->rx_skb;
15087cf6560SRémi Denis-Courmont 		if (!skb) {
15187cf6560SRémi Denis-Courmont 			skb = pnd->rx_skb = netdev_alloc_skb(dev, 12);
15287cf6560SRémi Denis-Courmont 			if (likely(skb)) {
15387cf6560SRémi Denis-Courmont 				/* Can't use pskb_pull() on page in IRQ */
15459ae1d12SJohannes Berg 				skb_put_data(skb, page_address(page), 1);
15587cf6560SRémi Denis-Courmont 				skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags,
15650269e19SEric Dumazet 						page, 1, req->actual_length,
157094b5855SEric Dumazet 						PAGE_SIZE);
15887cf6560SRémi Denis-Courmont 				page = NULL;
15987cf6560SRémi Denis-Courmont 			}
16087cf6560SRémi Denis-Courmont 		} else {
16187cf6560SRémi Denis-Courmont 			skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags,
16250269e19SEric Dumazet 					page, 0, req->actual_length,
163094b5855SEric Dumazet 					PAGE_SIZE);
16487cf6560SRémi Denis-Courmont 			page = NULL;
16587cf6560SRémi Denis-Courmont 		}
16687cf6560SRémi Denis-Courmont 		if (req->actual_length < PAGE_SIZE)
16787cf6560SRémi Denis-Courmont 			pnd->rx_skb = NULL; /* Last fragment */
16887cf6560SRémi Denis-Courmont 		else
16987cf6560SRémi Denis-Courmont 			skb = NULL;
17087cf6560SRémi Denis-Courmont 		spin_unlock_irqrestore(&pnd->rx_lock, flags);
17187cf6560SRémi Denis-Courmont 		if (skb) {
17287cf6560SRémi Denis-Courmont 			skb->protocol = htons(ETH_P_PHONET);
17387cf6560SRémi Denis-Courmont 			skb_reset_mac_header(skb);
17487cf6560SRémi Denis-Courmont 			__skb_pull(skb, 1);
17587cf6560SRémi Denis-Courmont 			skb->dev = dev;
17687cf6560SRémi Denis-Courmont 			dev->stats.rx_packets++;
17787cf6560SRémi Denis-Courmont 			dev->stats.rx_bytes += skb->len;
17887cf6560SRémi Denis-Courmont 
17987cf6560SRémi Denis-Courmont 			netif_rx(skb);
18087cf6560SRémi Denis-Courmont 		}
18187cf6560SRémi Denis-Courmont 		goto resubmit;
18287cf6560SRémi Denis-Courmont 
18387cf6560SRémi Denis-Courmont 	case -ENOENT:
18487cf6560SRémi Denis-Courmont 	case -ECONNRESET:
18587cf6560SRémi Denis-Courmont 	case -ESHUTDOWN:
18687cf6560SRémi Denis-Courmont 		req = NULL;
18787cf6560SRémi Denis-Courmont 		break;
18887cf6560SRémi Denis-Courmont 
18987cf6560SRémi Denis-Courmont 	case -EOVERFLOW:
19087cf6560SRémi Denis-Courmont 		dev->stats.rx_over_errors++;
19187cf6560SRémi Denis-Courmont 		dev_dbg(&dev->dev, "RX overflow\n");
19287cf6560SRémi Denis-Courmont 		break;
19387cf6560SRémi Denis-Courmont 
19487cf6560SRémi Denis-Courmont 	case -EILSEQ:
19587cf6560SRémi Denis-Courmont 		dev->stats.rx_crc_errors++;
19687cf6560SRémi Denis-Courmont 		break;
19787cf6560SRémi Denis-Courmont 	}
19887cf6560SRémi Denis-Courmont 
19987cf6560SRémi Denis-Courmont 	dev->stats.rx_errors++;
20087cf6560SRémi Denis-Courmont resubmit:
20187cf6560SRémi Denis-Courmont 	if (page)
2021f2149c1SEric Dumazet 		put_page(page);
20387cf6560SRémi Denis-Courmont 	if (req)
2045693d284SAlexander Duyck 		rx_submit(pnd, req, GFP_ATOMIC);
20587cf6560SRémi Denis-Courmont }
20687cf6560SRémi Denis-Courmont 
20787cf6560SRémi Denis-Courmont static int usbpn_close(struct net_device *dev);
20887cf6560SRémi Denis-Courmont 
usbpn_open(struct net_device * dev)20987cf6560SRémi Denis-Courmont static int usbpn_open(struct net_device *dev)
21087cf6560SRémi Denis-Courmont {
21187cf6560SRémi Denis-Courmont 	struct usbpn_dev *pnd = netdev_priv(dev);
21287cf6560SRémi Denis-Courmont 	int err;
21387cf6560SRémi Denis-Courmont 	unsigned i;
21487cf6560SRémi Denis-Courmont 	unsigned num = pnd->data_intf->cur_altsetting->desc.bInterfaceNumber;
21587cf6560SRémi Denis-Courmont 
21687cf6560SRémi Denis-Courmont 	err = usb_set_interface(pnd->usb, num, pnd->active_setting);
21787cf6560SRémi Denis-Courmont 	if (err)
21887cf6560SRémi Denis-Courmont 		return err;
21987cf6560SRémi Denis-Courmont 
22087cf6560SRémi Denis-Courmont 	for (i = 0; i < rxq_size; i++) {
22187cf6560SRémi Denis-Courmont 		struct urb *req = usb_alloc_urb(0, GFP_KERNEL);
22287cf6560SRémi Denis-Courmont 
2235693d284SAlexander Duyck 		if (!req || rx_submit(pnd, req, GFP_KERNEL)) {
22447dffc75SJesper Juhl 			usb_free_urb(req);
22587cf6560SRémi Denis-Courmont 			usbpn_close(dev);
22687cf6560SRémi Denis-Courmont 			return -ENOMEM;
22787cf6560SRémi Denis-Courmont 		}
22887cf6560SRémi Denis-Courmont 		pnd->urbs[i] = req;
22987cf6560SRémi Denis-Courmont 	}
23087cf6560SRémi Denis-Courmont 
23187cf6560SRémi Denis-Courmont 	netif_wake_queue(dev);
23287cf6560SRémi Denis-Courmont 	return 0;
23387cf6560SRémi Denis-Courmont }
23487cf6560SRémi Denis-Courmont 
usbpn_close(struct net_device * dev)23587cf6560SRémi Denis-Courmont static int usbpn_close(struct net_device *dev)
23687cf6560SRémi Denis-Courmont {
23787cf6560SRémi Denis-Courmont 	struct usbpn_dev *pnd = netdev_priv(dev);
23887cf6560SRémi Denis-Courmont 	unsigned i;
23987cf6560SRémi Denis-Courmont 	unsigned num = pnd->data_intf->cur_altsetting->desc.bInterfaceNumber;
24087cf6560SRémi Denis-Courmont 
24187cf6560SRémi Denis-Courmont 	netif_stop_queue(dev);
24287cf6560SRémi Denis-Courmont 
24387cf6560SRémi Denis-Courmont 	for (i = 0; i < rxq_size; i++) {
24487cf6560SRémi Denis-Courmont 		struct urb *req = pnd->urbs[i];
24587cf6560SRémi Denis-Courmont 
24687cf6560SRémi Denis-Courmont 		if (!req)
24787cf6560SRémi Denis-Courmont 			continue;
24887cf6560SRémi Denis-Courmont 		usb_kill_urb(req);
24987cf6560SRémi Denis-Courmont 		usb_free_urb(req);
25087cf6560SRémi Denis-Courmont 		pnd->urbs[i] = NULL;
25187cf6560SRémi Denis-Courmont 	}
25287cf6560SRémi Denis-Courmont 
25387cf6560SRémi Denis-Courmont 	return usb_set_interface(pnd->usb, num, !pnd->active_setting);
25487cf6560SRémi Denis-Courmont }
25587cf6560SRémi Denis-Courmont 
usbpn_siocdevprivate(struct net_device * dev,struct ifreq * ifr,void __user * data,int cmd)2564747c1a8SArnd Bergmann static int usbpn_siocdevprivate(struct net_device *dev, struct ifreq *ifr,
2574747c1a8SArnd Bergmann 				void __user *data, int cmd)
25802571f89SRémi Denis-Courmont {
25902571f89SRémi Denis-Courmont 	struct if_phonet_req *req = (struct if_phonet_req *)ifr;
26002571f89SRémi Denis-Courmont 
26102571f89SRémi Denis-Courmont 	switch (cmd) {
26202571f89SRémi Denis-Courmont 	case SIOCPNGAUTOCONF:
26302571f89SRémi Denis-Courmont 		req->ifr_phonet_autoconf.device = PN_DEV_PC;
26402571f89SRémi Denis-Courmont 		return 0;
26502571f89SRémi Denis-Courmont 	}
26602571f89SRémi Denis-Courmont 	return -ENOIOCTLCMD;
26702571f89SRémi Denis-Courmont }
26802571f89SRémi Denis-Courmont 
26987cf6560SRémi Denis-Courmont static const struct net_device_ops usbpn_ops = {
27087cf6560SRémi Denis-Courmont 	.ndo_open	= usbpn_open,
27187cf6560SRémi Denis-Courmont 	.ndo_stop	= usbpn_close,
27287cf6560SRémi Denis-Courmont 	.ndo_start_xmit = usbpn_xmit,
2734747c1a8SArnd Bergmann 	.ndo_siocdevprivate = usbpn_siocdevprivate,
27487cf6560SRémi Denis-Courmont };
27587cf6560SRémi Denis-Courmont 
usbpn_setup(struct net_device * dev)27687cf6560SRémi Denis-Courmont static void usbpn_setup(struct net_device *dev)
27787cf6560SRémi Denis-Courmont {
278*13b5ffa0SJakub Kicinski 	const u8 addr = PN_MEDIA_USB;
279*13b5ffa0SJakub Kicinski 
28087cf6560SRémi Denis-Courmont 	dev->features		= 0;
281752baafbSZheng Yongjun 	dev->netdev_ops		= &usbpn_ops;
28287cf6560SRémi Denis-Courmont 	dev->header_ops		= &phonet_header_ops;
28387cf6560SRémi Denis-Courmont 	dev->type		= ARPHRD_PHONET;
28487cf6560SRémi Denis-Courmont 	dev->flags		= IFF_POINTOPOINT | IFF_NOARP;
28587cf6560SRémi Denis-Courmont 	dev->mtu		= PHONET_MAX_MTU;
286f77f0aeeSJarod Wilson 	dev->min_mtu		= PHONET_MIN_MTU;
287f77f0aeeSJarod Wilson 	dev->max_mtu		= PHONET_MAX_MTU;
28887cf6560SRémi Denis-Courmont 	dev->hard_header_len	= 1;
28987cf6560SRémi Denis-Courmont 	dev->addr_len		= 1;
290*13b5ffa0SJakub Kicinski 	dev_addr_set(dev, &addr);
29187cf6560SRémi Denis-Courmont 	dev->tx_queue_len	= 3;
29287cf6560SRémi Denis-Courmont 
293cf124db5SDavid S. Miller 	dev->needs_free_netdev	= true;
29487cf6560SRémi Denis-Courmont }
29587cf6560SRémi Denis-Courmont 
29687cf6560SRémi Denis-Courmont /*
29787cf6560SRémi Denis-Courmont  * USB driver callbacks
29887cf6560SRémi Denis-Courmont  */
2997f04c61dSArvind Yadav static const struct usb_device_id usbpn_ids[] = {
30087cf6560SRémi Denis-Courmont 	{
30187cf6560SRémi Denis-Courmont 		.match_flags = USB_DEVICE_ID_MATCH_VENDOR
30287cf6560SRémi Denis-Courmont 			| USB_DEVICE_ID_MATCH_INT_CLASS
30387cf6560SRémi Denis-Courmont 			| USB_DEVICE_ID_MATCH_INT_SUBCLASS,
30487cf6560SRémi Denis-Courmont 		.idVendor = 0x0421, /* Nokia */
30587cf6560SRémi Denis-Courmont 		.bInterfaceClass = USB_CLASS_COMM,
30687cf6560SRémi Denis-Courmont 		.bInterfaceSubClass = 0xFE,
30787cf6560SRémi Denis-Courmont 	},
30887cf6560SRémi Denis-Courmont 	{ },
30987cf6560SRémi Denis-Courmont };
31087cf6560SRémi Denis-Courmont 
31187cf6560SRémi Denis-Courmont MODULE_DEVICE_TABLE(usb, usbpn_ids);
31287cf6560SRémi Denis-Courmont 
31387cf6560SRémi Denis-Courmont static struct usb_driver usbpn_driver;
31487cf6560SRémi Denis-Courmont 
usbpn_probe(struct usb_interface * intf,const struct usb_device_id * id)315052c19e6SSachin Kamat static int usbpn_probe(struct usb_interface *intf, const struct usb_device_id *id)
31687cf6560SRémi Denis-Courmont {
31787cf6560SRémi Denis-Courmont 	static const char ifname[] = "usbpn%d";
31887cf6560SRémi Denis-Courmont 	const struct usb_cdc_union_desc *union_header = NULL;
31987cf6560SRémi Denis-Courmont 	const struct usb_host_interface *data_desc;
32087cf6560SRémi Denis-Courmont 	struct usb_interface *data_intf;
32187cf6560SRémi Denis-Courmont 	struct usb_device *usbdev = interface_to_usbdev(intf);
32287cf6560SRémi Denis-Courmont 	struct net_device *dev;
32387cf6560SRémi Denis-Courmont 	struct usbpn_dev *pnd;
32487cf6560SRémi Denis-Courmont 	u8 *data;
325468c3f92SJiri Slaby 	int phonet = 0;
32687cf6560SRémi Denis-Courmont 	int len, err;
3277b6ee48dSOliver Neukum 	struct usb_cdc_parsed_header hdr;
32887cf6560SRémi Denis-Courmont 
32987cf6560SRémi Denis-Courmont 	data = intf->altsetting->extra;
33087cf6560SRémi Denis-Courmont 	len = intf->altsetting->extralen;
3317b6ee48dSOliver Neukum 	cdc_parse_cdc_header(&hdr, intf, data, len);
3327b6ee48dSOliver Neukum 	union_header = hdr.usb_cdc_union_desc;
3337b6ee48dSOliver Neukum 	phonet = hdr.phonet_magic_present;
33487cf6560SRémi Denis-Courmont 
335468c3f92SJiri Slaby 	if (!union_header || !phonet)
33687cf6560SRémi Denis-Courmont 		return -EINVAL;
33787cf6560SRémi Denis-Courmont 
33887cf6560SRémi Denis-Courmont 	data_intf = usb_ifnum_to_if(usbdev, union_header->bSlaveInterface0);
33987cf6560SRémi Denis-Courmont 	if (data_intf == NULL)
34087cf6560SRémi Denis-Courmont 		return -ENODEV;
34187cf6560SRémi Denis-Courmont 	/* Data interface has one inactive and one active setting */
34287cf6560SRémi Denis-Courmont 	if (data_intf->num_altsetting != 2)
34387cf6560SRémi Denis-Courmont 		return -EINVAL;
3448e95a202SJoe Perches 	if (data_intf->altsetting[0].desc.bNumEndpoints == 0 &&
3458e95a202SJoe Perches 	    data_intf->altsetting[1].desc.bNumEndpoints == 2)
34687cf6560SRémi Denis-Courmont 		data_desc = data_intf->altsetting + 1;
34787cf6560SRémi Denis-Courmont 	else
3488e95a202SJoe Perches 	if (data_intf->altsetting[0].desc.bNumEndpoints == 2 &&
3498e95a202SJoe Perches 	    data_intf->altsetting[1].desc.bNumEndpoints == 0)
35087cf6560SRémi Denis-Courmont 		data_desc = data_intf->altsetting;
35187cf6560SRémi Denis-Courmont 	else
35287cf6560SRémi Denis-Courmont 		return -EINVAL;
35387cf6560SRémi Denis-Courmont 
354fd6d1226SGustavo A. R. Silva 	dev = alloc_netdev(struct_size(pnd, urbs, rxq_size), ifname,
355fd6d1226SGustavo A. R. Silva 			   NET_NAME_UNKNOWN, usbpn_setup);
35687cf6560SRémi Denis-Courmont 	if (!dev)
35787cf6560SRémi Denis-Courmont 		return -ENOMEM;
35887cf6560SRémi Denis-Courmont 
35987cf6560SRémi Denis-Courmont 	pnd = netdev_priv(dev);
36087cf6560SRémi Denis-Courmont 	SET_NETDEV_DEV(dev, &intf->dev);
36187cf6560SRémi Denis-Courmont 
36287cf6560SRémi Denis-Courmont 	pnd->dev = dev;
36350e7d153Stom.leiming@gmail.com 	pnd->usb = usbdev;
36487cf6560SRémi Denis-Courmont 	pnd->intf = intf;
36587cf6560SRémi Denis-Courmont 	pnd->data_intf = data_intf;
36687cf6560SRémi Denis-Courmont 	spin_lock_init(&pnd->tx_lock);
36787cf6560SRémi Denis-Courmont 	spin_lock_init(&pnd->rx_lock);
36887cf6560SRémi Denis-Courmont 	/* Endpoints */
36987cf6560SRémi Denis-Courmont 	if (usb_pipein(data_desc->endpoint[0].desc.bEndpointAddress)) {
37087cf6560SRémi Denis-Courmont 		pnd->rx_pipe = usb_rcvbulkpipe(usbdev,
37187cf6560SRémi Denis-Courmont 			data_desc->endpoint[0].desc.bEndpointAddress);
37287cf6560SRémi Denis-Courmont 		pnd->tx_pipe = usb_sndbulkpipe(usbdev,
37387cf6560SRémi Denis-Courmont 			data_desc->endpoint[1].desc.bEndpointAddress);
37487cf6560SRémi Denis-Courmont 	} else {
37587cf6560SRémi Denis-Courmont 		pnd->rx_pipe = usb_rcvbulkpipe(usbdev,
37687cf6560SRémi Denis-Courmont 			data_desc->endpoint[1].desc.bEndpointAddress);
37787cf6560SRémi Denis-Courmont 		pnd->tx_pipe = usb_sndbulkpipe(usbdev,
37887cf6560SRémi Denis-Courmont 			data_desc->endpoint[0].desc.bEndpointAddress);
37987cf6560SRémi Denis-Courmont 	}
38087cf6560SRémi Denis-Courmont 	pnd->active_setting = data_desc - data_intf->altsetting;
38187cf6560SRémi Denis-Courmont 
38287cf6560SRémi Denis-Courmont 	err = usb_driver_claim_interface(&usbpn_driver, data_intf, pnd);
38387cf6560SRémi Denis-Courmont 	if (err)
38487cf6560SRémi Denis-Courmont 		goto out;
38587cf6560SRémi Denis-Courmont 
38687cf6560SRémi Denis-Courmont 	/* Force inactive mode until the network device is brought UP */
38787cf6560SRémi Denis-Courmont 	usb_set_interface(usbdev, union_header->bSlaveInterface0,
38887cf6560SRémi Denis-Courmont 				!pnd->active_setting);
38987cf6560SRémi Denis-Courmont 	usb_set_intfdata(intf, pnd);
39087cf6560SRémi Denis-Courmont 
39187cf6560SRémi Denis-Courmont 	err = register_netdev(dev);
39287cf6560SRémi Denis-Courmont 	if (err) {
393c79a7070SJohan Hovold 		/* Set disconnected flag so that disconnect() returns early. */
394c79a7070SJohan Hovold 		pnd->disconnected = 1;
39587cf6560SRémi Denis-Courmont 		usb_driver_release_interface(&usbpn_driver, data_intf);
39687cf6560SRémi Denis-Courmont 		goto out;
39787cf6560SRémi Denis-Courmont 	}
39887cf6560SRémi Denis-Courmont 
39987cf6560SRémi Denis-Courmont 	dev_dbg(&dev->dev, "USB CDC Phonet device found\n");
40087cf6560SRémi Denis-Courmont 	return 0;
40187cf6560SRémi Denis-Courmont 
40287cf6560SRémi Denis-Courmont out:
40387cf6560SRémi Denis-Courmont 	usb_set_intfdata(intf, NULL);
40487cf6560SRémi Denis-Courmont 	free_netdev(dev);
40587cf6560SRémi Denis-Courmont 	return err;
40687cf6560SRémi Denis-Courmont }
40787cf6560SRémi Denis-Courmont 
usbpn_disconnect(struct usb_interface * intf)40887cf6560SRémi Denis-Courmont static void usbpn_disconnect(struct usb_interface *intf)
40987cf6560SRémi Denis-Courmont {
41087cf6560SRémi Denis-Courmont 	struct usbpn_dev *pnd = usb_get_intfdata(intf);
41187cf6560SRémi Denis-Courmont 
41287cf6560SRémi Denis-Courmont 	if (pnd->disconnected)
41387cf6560SRémi Denis-Courmont 		return;
41487cf6560SRémi Denis-Courmont 
41587cf6560SRémi Denis-Courmont 	pnd->disconnected = 1;
41687cf6560SRémi Denis-Courmont 	usb_driver_release_interface(&usbpn_driver,
41787cf6560SRémi Denis-Courmont 			(pnd->intf == intf) ? pnd->data_intf : pnd->intf);
41887cf6560SRémi Denis-Courmont 	unregister_netdev(pnd->dev);
41987cf6560SRémi Denis-Courmont }
42087cf6560SRémi Denis-Courmont 
42187cf6560SRémi Denis-Courmont static struct usb_driver usbpn_driver = {
42287cf6560SRémi Denis-Courmont 	.name =		"cdc_phonet",
42387cf6560SRémi Denis-Courmont 	.probe =	usbpn_probe,
42487cf6560SRémi Denis-Courmont 	.disconnect =	usbpn_disconnect,
42587cf6560SRémi Denis-Courmont 	.id_table =	usbpn_ids,
426e1f12eb6SSarah Sharp 	.disable_hub_initiated_lpm = 1,
42787cf6560SRémi Denis-Courmont };
42887cf6560SRémi Denis-Courmont 
429d632eb1bSGreg Kroah-Hartman module_usb_driver(usbpn_driver);
43087cf6560SRémi Denis-Courmont 
43187cf6560SRémi Denis-Courmont MODULE_AUTHOR("Remi Denis-Courmont");
43287cf6560SRémi Denis-Courmont MODULE_DESCRIPTION("USB CDC Phonet host interface");
43387cf6560SRémi Denis-Courmont MODULE_LICENSE("GPL");
434