blob: 2f21139466ecf18b088588d2ba8938f207995ddb [file] [log] [blame]
Greg Suarez9bf211a32012-10-22 10:56:36 +00001/*
2 * Copyright (c) 2012 Smith Micro Software, Inc.
3 * Copyright (c) 2012 Bjørn Mork <bjorn@mork.no>
4 *
5 * This driver is based on and reuse most of cdc_ncm, which is
6 * Copyright (C) ST-Ericsson 2010-2012
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * version 2 as published by the Free Software Foundation.
11 */
12
13#include <linux/module.h>
14#include <linux/netdevice.h>
15#include <linux/ethtool.h>
16#include <linux/ip.h>
17#include <linux/mii.h>
18#include <linux/usb.h>
19#include <linux/usb/cdc.h>
20#include <linux/usb/usbnet.h>
21#include <linux/usb/cdc-wdm.h>
22#include <linux/usb/cdc_ncm.h>
23
24/* driver specific data - must match cdc_ncm usage */
25struct cdc_mbim_state {
26 struct cdc_ncm_ctx *ctx;
27 atomic_t pmcount;
28 struct usb_driver *subdriver;
29 struct usb_interface *control;
30 struct usb_interface *data;
31};
32
33/* using a counter to merge subdriver requests with our own into a combined state */
34static int cdc_mbim_manage_power(struct usbnet *dev, int on)
35{
36 struct cdc_mbim_state *info = (void *)&dev->data;
37 int rv = 0;
38
39 dev_dbg(&dev->intf->dev, "%s() pmcount=%d, on=%d\n", __func__, atomic_read(&info->pmcount), on);
40
41 if ((on && atomic_add_return(1, &info->pmcount) == 1) || (!on && atomic_dec_and_test(&info->pmcount))) {
42 /* need autopm_get/put here to ensure the usbcore sees the new value */
43 rv = usb_autopm_get_interface(dev->intf);
44 if (rv < 0)
45 goto err;
46 dev->intf->needs_remote_wakeup = on;
47 usb_autopm_put_interface(dev->intf);
48 }
49err:
50 return rv;
51}
52
53static int cdc_mbim_wdm_manage_power(struct usb_interface *intf, int status)
54{
55 struct usbnet *dev = usb_get_intfdata(intf);
56
57 /* can be called while disconnecting */
58 if (!dev)
59 return 0;
60
61 return cdc_mbim_manage_power(dev, status);
62}
63
64
65static int cdc_mbim_bind(struct usbnet *dev, struct usb_interface *intf)
66{
67 struct cdc_ncm_ctx *ctx;
68 struct usb_driver *subdriver = ERR_PTR(-ENODEV);
69 int ret = -ENODEV;
70 u8 data_altsetting = CDC_NCM_DATA_ALTSETTING_NCM;
71 struct cdc_mbim_state *info = (void *)&dev->data;
72
73 /* see if interface supports MBIM alternate setting */
74 if (intf->num_altsetting == 2) {
75 if (!cdc_ncm_comm_intf_is_mbim(intf->cur_altsetting))
76 usb_set_interface(dev->udev,
77 intf->cur_altsetting->desc.bInterfaceNumber,
78 CDC_NCM_COMM_ALTSETTING_MBIM);
79 data_altsetting = CDC_NCM_DATA_ALTSETTING_MBIM;
80 }
81
82 /* Probably NCM, defer for cdc_ncm_bind */
83 if (!cdc_ncm_comm_intf_is_mbim(intf->cur_altsetting))
84 goto err;
85
86 ret = cdc_ncm_bind_common(dev, intf, data_altsetting);
87 if (ret)
88 goto err;
89
90 ctx = info->ctx;
91
92 /* The MBIM descriptor and the status endpoint are required */
93 if (ctx->mbim_desc && dev->status)
94 subdriver = usb_cdc_wdm_register(ctx->control,
95 &dev->status->desc,
96 le16_to_cpu(ctx->mbim_desc->wMaxControlMessage),
97 cdc_mbim_wdm_manage_power);
98 if (IS_ERR(subdriver)) {
99 ret = PTR_ERR(subdriver);
100 cdc_ncm_unbind(dev, intf);
101 goto err;
102 }
103
104 /* can't let usbnet use the interrupt endpoint */
105 dev->status = NULL;
106 info->subdriver = subdriver;
107
108 /* MBIM cannot do ARP */
109 dev->net->flags |= IFF_NOARP;
110err:
111 return ret;
112}
113
114static void cdc_mbim_unbind(struct usbnet *dev, struct usb_interface *intf)
115{
116 struct cdc_mbim_state *info = (void *)&dev->data;
117 struct cdc_ncm_ctx *ctx = info->ctx;
118
119 /* disconnect subdriver from control interface */
120 if (info->subdriver && info->subdriver->disconnect)
121 info->subdriver->disconnect(ctx->control);
122 info->subdriver = NULL;
123
124 /* let NCM unbind clean up both control and data interface */
125 cdc_ncm_unbind(dev, intf);
126}
127
128
129static struct sk_buff *cdc_mbim_tx_fixup(struct usbnet *dev, struct sk_buff *skb, gfp_t flags)
130{
131 struct sk_buff *skb_out;
132 struct cdc_mbim_state *info = (void *)&dev->data;
133 struct cdc_ncm_ctx *ctx = info->ctx;
134
135 if (!ctx)
136 goto error;
137
138 if (skb) {
139 if (skb->len <= sizeof(ETH_HLEN))
140 goto error;
141
142 skb_reset_mac_header(skb);
143 switch (eth_hdr(skb)->h_proto) {
144 case htons(ETH_P_IP):
145 case htons(ETH_P_IPV6):
146 skb_pull(skb, ETH_HLEN);
147 break;
148 default:
149 goto error;
150 }
151 }
152
153 spin_lock_bh(&ctx->mtx);
154 skb_out = cdc_ncm_fill_tx_frame(ctx, skb, cpu_to_le32(USB_CDC_MBIM_NDP16_IPS_SIGN));
155 spin_unlock_bh(&ctx->mtx);
156 return skb_out;
157
158error:
159 if (skb)
160 dev_kfree_skb_any(skb);
161
162 return NULL;
163}
164
165static struct sk_buff *cdc_mbim_process_dgram(struct usbnet *dev, u8 *buf, size_t len)
166{
167 __be16 proto;
168 struct sk_buff *skb = NULL;
169
170 switch (*buf & 0xf0) {
171 case 0x40:
172 proto = htons(ETH_P_IP);
173 break;
174 case 0x60:
175 proto = htons(ETH_P_IPV6);
176 break;
177 default:
178 goto err;
179 }
180
181 skb = netdev_alloc_skb_ip_align(dev->net, len + ETH_HLEN);
182 if (!skb)
183 goto err;
184
185 /* add an ethernet header */
186 skb_put(skb, ETH_HLEN);
187 skb_reset_mac_header(skb);
188 eth_hdr(skb)->h_proto = proto;
189 memset(eth_hdr(skb)->h_source, 0, ETH_ALEN);
190 memcpy(eth_hdr(skb)->h_dest, dev->net->dev_addr, ETH_ALEN);
191
192 /* add datagram */
193 memcpy(skb_put(skb, len), buf, len);
194err:
195 return skb;
196}
197
198static int cdc_mbim_rx_fixup(struct usbnet *dev, struct sk_buff *skb_in)
199{
200 struct sk_buff *skb;
201 struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0];
202 int len;
203 int nframes;
204 int x;
205 int offset;
206 struct usb_cdc_ncm_ndp16 *ndp16;
207 struct usb_cdc_ncm_dpe16 *dpe16;
208 int ndpoffset;
209 int loopcount = 50; /* arbitrary max preventing infinite loop */
210
211 ndpoffset = cdc_ncm_rx_verify_nth16(ctx, skb_in);
212 if (ndpoffset < 0)
213 goto error;
214
215next_ndp:
216 nframes = cdc_ncm_rx_verify_ndp16(skb_in, ndpoffset);
217 if (nframes < 0)
218 goto error;
219
220 ndp16 = (struct usb_cdc_ncm_ndp16 *)(skb_in->data + ndpoffset);
221
222 /* only supporting IPS Session #0 for now */
223 switch (ndp16->dwSignature) {
224 case cpu_to_le32(USB_CDC_MBIM_NDP16_IPS_SIGN):
225 break;
226 default:
227 netif_dbg(dev, rx_err, dev->net,
228 "unsupported NDP signature <0x%08x>\n",
229 le32_to_cpu(ndp16->dwSignature));
230 goto err_ndp;
231
232 }
233
234 dpe16 = ndp16->dpe16;
235 for (x = 0; x < nframes; x++, dpe16++) {
236 offset = le16_to_cpu(dpe16->wDatagramIndex);
237 len = le16_to_cpu(dpe16->wDatagramLength);
238
239 /*
240 * CDC NCM ch. 3.7
241 * All entries after first NULL entry are to be ignored
242 */
243 if ((offset == 0) || (len == 0)) {
244 if (!x)
245 goto err_ndp; /* empty NTB */
246 break;
247 }
248
249 /* sanity checking */
250 if (((offset + len) > skb_in->len) || (len > ctx->rx_max) ||
251 (len < sizeof(struct iphdr))) {
252 netif_dbg(dev, rx_err, dev->net,
253 "invalid frame detected (ignored) offset[%u]=%u, length=%u, skb=%p\n",
254 x, offset, len, skb_in);
255 if (!x)
256 goto err_ndp;
257 break;
258 } else {
259 skb = cdc_mbim_process_dgram(dev, skb_in->data + offset, len);
260 if (!skb)
261 goto error;
262 usbnet_skb_return(dev, skb);
263 }
264 }
265err_ndp:
266 /* are there more NDPs to process? */
267 ndpoffset = le16_to_cpu(ndp16->wNextNdpIndex);
268 if (ndpoffset && loopcount--)
269 goto next_ndp;
270
271 return 1;
272error:
273 return 0;
274}
275
276static int cdc_mbim_suspend(struct usb_interface *intf, pm_message_t message)
277{
278 int ret = 0;
279 struct usbnet *dev = usb_get_intfdata(intf);
280 struct cdc_mbim_state *info = (void *)&dev->data;
281 struct cdc_ncm_ctx *ctx = info->ctx;
282
283 if (ctx == NULL) {
284 ret = -1;
285 goto error;
286 }
287
288 ret = usbnet_suspend(intf, message);
289 if (ret < 0)
290 goto error;
291
292 if (intf == ctx->control && info->subdriver && info->subdriver->suspend)
293 ret = info->subdriver->suspend(intf, message);
294 if (ret < 0)
295 usbnet_resume(intf);
296
297error:
298 return ret;
299}
300
301static int cdc_mbim_resume(struct usb_interface *intf)
302{
303 int ret = 0;
304 struct usbnet *dev = usb_get_intfdata(intf);
305 struct cdc_mbim_state *info = (void *)&dev->data;
306 struct cdc_ncm_ctx *ctx = info->ctx;
307 bool callsub = (intf == ctx->control && info->subdriver && info->subdriver->resume);
308
309 if (callsub)
310 ret = info->subdriver->resume(intf);
311 if (ret < 0)
312 goto err;
313 ret = usbnet_resume(intf);
314 if (ret < 0 && callsub && info->subdriver->suspend)
315 info->subdriver->suspend(intf, PMSG_SUSPEND);
316err:
317 return ret;
318}
319
320static const struct driver_info cdc_mbim_info = {
321 .description = "CDC MBIM",
322 .flags = FLAG_NO_SETINT | FLAG_MULTI_PACKET | FLAG_WWAN,
323 .bind = cdc_mbim_bind,
324 .unbind = cdc_mbim_unbind,
325 .manage_power = cdc_mbim_manage_power,
326 .rx_fixup = cdc_mbim_rx_fixup,
327 .tx_fixup = cdc_mbim_tx_fixup,
328};
329
330static const struct usb_device_id mbim_devs[] = {
331 /* This duplicate NCM entry is intentional. MBIM devices can
332 * be disguised as NCM by default, and this is necessary to
333 * allow us to bind the correct driver_info to such devices.
334 *
335 * bind() will sort out this for us, selecting the correct
336 * entry and reject the other
337 */
338 { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_NCM, USB_CDC_PROTO_NONE),
339 .driver_info = (unsigned long)&cdc_mbim_info,
340 },
341 { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_MBIM, USB_CDC_PROTO_NONE),
342 .driver_info = (unsigned long)&cdc_mbim_info,
343 },
344 {
345 },
346};
347MODULE_DEVICE_TABLE(usb, mbim_devs);
348
349static struct usb_driver cdc_mbim_driver = {
350 .name = "cdc_mbim",
351 .id_table = mbim_devs,
352 .probe = usbnet_probe,
353 .disconnect = usbnet_disconnect,
354 .suspend = cdc_mbim_suspend,
355 .resume = cdc_mbim_resume,
356 .reset_resume = cdc_mbim_resume,
357 .supports_autosuspend = 1,
358 .disable_hub_initiated_lpm = 1,
359};
360module_usb_driver(cdc_mbim_driver);
361
362MODULE_AUTHOR("Greg Suarez <gsuarez@smithmicro.com>");
363MODULE_AUTHOR("Bjørn Mork <bjorn@mork.no>");
364MODULE_DESCRIPTION("USB CDC MBIM host driver");
365MODULE_LICENSE("GPL");