1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * USB network interface driver for Samsung Kalmia based LTE USB modem like the |
4 | * Samsung GT-B3730 and GT-B3710. |
5 | * |
6 | * Copyright (C) 2011 Marius Bjoernstad Kotsbak <marius@kotsbak.com> |
7 | * |
8 | * Sponsored by Quicklink Video Distribution Services Ltd. |
9 | * |
10 | * Based on the cdc_eem module. |
11 | */ |
12 | |
13 | #include <linux/module.h> |
14 | #include <linux/netdevice.h> |
15 | #include <linux/etherdevice.h> |
16 | #include <linux/ctype.h> |
17 | #include <linux/ethtool.h> |
18 | #include <linux/workqueue.h> |
19 | #include <linux/mii.h> |
20 | #include <linux/usb.h> |
21 | #include <linux/crc32.h> |
22 | #include <linux/usb/cdc.h> |
23 | #include <linux/usb/usbnet.h> |
24 | #include <linux/gfp.h> |
25 | |
26 | /* |
27 | * The Samsung Kalmia based LTE USB modems have a CDC ACM port for modem control |
28 | * handled by the "option" module and an ethernet data port handled by this |
29 | * module. |
30 | * |
31 | * The stick must first be switched into modem mode by usb_modeswitch |
32 | * or similar tool. Then the modem gets sent two initialization packets by |
33 | * this module, which gives the MAC address of the device. User space can then |
34 | * connect the modem using AT commands through the ACM port and then use |
35 | * DHCP on the network interface exposed by this module. Network packets are |
36 | * sent to and from the modem in a proprietary format discovered after watching |
37 | * the behavior of the windows driver for the modem. |
38 | * |
39 | * More information about the use of the modem is available in usb_modeswitch |
40 | * forum and the project page: |
41 | * |
42 | * http://www.draisberghof.de/usb_modeswitch/bb/viewtopic.php?t=465 |
43 | * https://github.com/mkotsbak/Samsung-GT-B3730-linux-driver |
44 | */ |
45 | |
46 | /* #define DEBUG */ |
47 | /* #define VERBOSE */ |
48 | |
49 | #define 6 |
50 | #define KALMIA_ALIGN_SIZE 4 |
51 | #define KALMIA_USB_TIMEOUT 10000 |
52 | |
53 | /*-------------------------------------------------------------------------*/ |
54 | |
55 | static int |
56 | kalmia_send_init_packet(struct usbnet *dev, u8 *init_msg, u8 init_msg_len, |
57 | u8 *buffer, u8 expected_len) |
58 | { |
59 | int act_len; |
60 | int status; |
61 | |
62 | netdev_dbg(dev->net, "Sending init packet" ); |
63 | |
64 | status = usb_bulk_msg(usb_dev: dev->udev, usb_sndbulkpipe(dev->udev, 0x02), |
65 | data: init_msg, len: init_msg_len, actual_length: &act_len, KALMIA_USB_TIMEOUT); |
66 | if (status != 0) { |
67 | netdev_err(dev: dev->net, |
68 | format: "Error sending init packet. Status %i\n" , |
69 | status); |
70 | return status; |
71 | } |
72 | else if (act_len != init_msg_len) { |
73 | netdev_err(dev: dev->net, |
74 | format: "Did not send all of init packet. Bytes sent: %i" , |
75 | act_len); |
76 | } |
77 | else { |
78 | netdev_dbg(dev->net, "Successfully sent init packet." ); |
79 | } |
80 | |
81 | status = usb_bulk_msg(usb_dev: dev->udev, usb_rcvbulkpipe(dev->udev, 0x81), |
82 | data: buffer, len: expected_len, actual_length: &act_len, KALMIA_USB_TIMEOUT); |
83 | |
84 | if (status != 0) |
85 | netdev_err(dev: dev->net, |
86 | format: "Error receiving init result. Status %i\n" , |
87 | status); |
88 | else if (act_len != expected_len) |
89 | netdev_err(dev: dev->net, format: "Unexpected init result length: %i\n" , |
90 | act_len); |
91 | |
92 | return status; |
93 | } |
94 | |
95 | static int |
96 | kalmia_init_and_get_ethernet_addr(struct usbnet *dev, u8 *ethernet_addr) |
97 | { |
98 | static const char init_msg_1[] = |
99 | { 0x57, 0x50, 0x04, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, |
100 | 0x00, 0x00 }; |
101 | static const char init_msg_2[] = |
102 | { 0x57, 0x50, 0x04, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0xf4, |
103 | 0x00, 0x00 }; |
104 | static const int buflen = 28; |
105 | char *usb_buf; |
106 | int status; |
107 | |
108 | usb_buf = kmalloc(size: buflen, GFP_DMA | GFP_KERNEL); |
109 | if (!usb_buf) |
110 | return -ENOMEM; |
111 | |
112 | memcpy(usb_buf, init_msg_1, 12); |
113 | status = kalmia_send_init_packet(dev, init_msg: usb_buf, ARRAY_SIZE(init_msg_1), |
114 | buffer: usb_buf, expected_len: 24); |
115 | if (status != 0) |
116 | goto out; |
117 | |
118 | memcpy(usb_buf, init_msg_2, 12); |
119 | status = kalmia_send_init_packet(dev, init_msg: usb_buf, ARRAY_SIZE(init_msg_2), |
120 | buffer: usb_buf, expected_len: 28); |
121 | if (status != 0) |
122 | goto out; |
123 | |
124 | memcpy(ethernet_addr, usb_buf + 10, ETH_ALEN); |
125 | out: |
126 | kfree(objp: usb_buf); |
127 | return status; |
128 | } |
129 | |
130 | static int |
131 | kalmia_bind(struct usbnet *dev, struct usb_interface *intf) |
132 | { |
133 | int status; |
134 | u8 ethernet_addr[ETH_ALEN]; |
135 | |
136 | /* Don't bind to AT command interface */ |
137 | if (intf->cur_altsetting->desc.bInterfaceClass != USB_CLASS_VENDOR_SPEC) |
138 | return -EINVAL; |
139 | |
140 | dev->in = usb_rcvbulkpipe(dev->udev, 0x81 & USB_ENDPOINT_NUMBER_MASK); |
141 | dev->out = usb_sndbulkpipe(dev->udev, 0x02 & USB_ENDPOINT_NUMBER_MASK); |
142 | dev->status = NULL; |
143 | |
144 | dev->net->hard_header_len += KALMIA_HEADER_LENGTH; |
145 | dev->hard_mtu = 1400; |
146 | dev->rx_urb_size = dev->hard_mtu * 10; // Found as optimal after testing |
147 | |
148 | status = kalmia_init_and_get_ethernet_addr(dev, ethernet_addr); |
149 | if (status) |
150 | return status; |
151 | |
152 | eth_hw_addr_set(dev: dev->net, addr: ethernet_addr); |
153 | |
154 | return status; |
155 | } |
156 | |
157 | static struct sk_buff * |
158 | kalmia_tx_fixup(struct usbnet *dev, struct sk_buff *skb, gfp_t flags) |
159 | { |
160 | struct sk_buff *skb2 = NULL; |
161 | u16 content_len; |
162 | unsigned char *; |
163 | unsigned char ether_type_1, ether_type_2; |
164 | u8 remainder, padlen = 0; |
165 | |
166 | if (!skb_cloned(skb)) { |
167 | int headroom = skb_headroom(skb); |
168 | int tailroom = skb_tailroom(skb); |
169 | |
170 | if ((tailroom >= KALMIA_ALIGN_SIZE) && (headroom |
171 | >= KALMIA_HEADER_LENGTH)) |
172 | goto done; |
173 | |
174 | if ((headroom + tailroom) > (KALMIA_HEADER_LENGTH |
175 | + KALMIA_ALIGN_SIZE)) { |
176 | skb->data = memmove(skb->head + KALMIA_HEADER_LENGTH, |
177 | skb->data, skb->len); |
178 | skb_set_tail_pointer(skb, offset: skb->len); |
179 | goto done; |
180 | } |
181 | } |
182 | |
183 | skb2 = skb_copy_expand(skb, KALMIA_HEADER_LENGTH, |
184 | KALMIA_ALIGN_SIZE, priority: flags); |
185 | if (!skb2) |
186 | return NULL; |
187 | |
188 | dev_kfree_skb_any(skb); |
189 | skb = skb2; |
190 | |
191 | done: |
192 | header_start = skb_push(skb, KALMIA_HEADER_LENGTH); |
193 | ether_type_1 = header_start[KALMIA_HEADER_LENGTH + 12]; |
194 | ether_type_2 = header_start[KALMIA_HEADER_LENGTH + 13]; |
195 | |
196 | netdev_dbg(dev->net, "Sending etherType: %02x%02x" , ether_type_1, |
197 | ether_type_2); |
198 | |
199 | /* According to empiric data for data packages */ |
200 | header_start[0] = 0x57; |
201 | header_start[1] = 0x44; |
202 | content_len = skb->len - KALMIA_HEADER_LENGTH; |
203 | |
204 | put_unaligned_le16(val: content_len, p: &header_start[2]); |
205 | header_start[4] = ether_type_1; |
206 | header_start[5] = ether_type_2; |
207 | |
208 | /* Align to 4 bytes by padding with zeros */ |
209 | remainder = skb->len % KALMIA_ALIGN_SIZE; |
210 | if (remainder > 0) { |
211 | padlen = KALMIA_ALIGN_SIZE - remainder; |
212 | skb_put_zero(skb, len: padlen); |
213 | } |
214 | |
215 | netdev_dbg(dev->net, |
216 | "Sending package with length %i and padding %i. Header: %6phC." , |
217 | content_len, padlen, header_start); |
218 | |
219 | return skb; |
220 | } |
221 | |
222 | static int |
223 | kalmia_rx_fixup(struct usbnet *dev, struct sk_buff *skb) |
224 | { |
225 | /* |
226 | * Our task here is to strip off framing, leaving skb with one |
227 | * data frame for the usbnet framework code to process. |
228 | */ |
229 | static const u8 [] = |
230 | { 0x57, 0x5a, 0x00, 0x00, 0x08, 0x00 }; |
231 | static const u8 [] = |
232 | { 0x57, 0x43, 0x1e, 0x00, 0x15, 0x02 }; |
233 | static const u8 [] = |
234 | { 0x57, 0x50, 0x0e, 0x00, 0x00, 0x00 }; |
235 | int i = 0; |
236 | |
237 | /* incomplete header? */ |
238 | if (skb->len < KALMIA_HEADER_LENGTH) |
239 | return 0; |
240 | |
241 | do { |
242 | struct sk_buff *skb2 = NULL; |
243 | u8 *; |
244 | u16 usb_packet_length, ether_packet_length; |
245 | int is_last; |
246 | |
247 | header_start = skb->data; |
248 | |
249 | if (unlikely(header_start[0] != 0x57 || header_start[1] != 0x44)) { |
250 | if (!memcmp(p: header_start, q: EXPECTED_UNKNOWN_HEADER_1, |
251 | size: sizeof(EXPECTED_UNKNOWN_HEADER_1)) || !memcmp( |
252 | p: header_start, q: EXPECTED_UNKNOWN_HEADER_2, |
253 | size: sizeof(EXPECTED_UNKNOWN_HEADER_2))) { |
254 | netdev_dbg(dev->net, |
255 | "Received expected unknown frame header: %6phC. Package length: %i\n" , |
256 | header_start, |
257 | skb->len - KALMIA_HEADER_LENGTH); |
258 | } |
259 | else { |
260 | netdev_err(dev: dev->net, |
261 | format: "Received unknown frame header: %6phC. Package length: %i\n" , |
262 | header_start, |
263 | skb->len - KALMIA_HEADER_LENGTH); |
264 | return 0; |
265 | } |
266 | } |
267 | else |
268 | netdev_dbg(dev->net, |
269 | "Received header: %6phC. Package length: %i\n" , |
270 | header_start, skb->len - KALMIA_HEADER_LENGTH); |
271 | |
272 | /* subtract start header and end header */ |
273 | usb_packet_length = skb->len - (2 * KALMIA_HEADER_LENGTH); |
274 | ether_packet_length = get_unaligned_le16(p: &header_start[2]); |
275 | skb_pull(skb, KALMIA_HEADER_LENGTH); |
276 | |
277 | /* Some small packets misses end marker */ |
278 | if (usb_packet_length < ether_packet_length) { |
279 | ether_packet_length = usb_packet_length |
280 | + KALMIA_HEADER_LENGTH; |
281 | is_last = true; |
282 | } |
283 | else { |
284 | netdev_dbg(dev->net, "Correct package length #%i" , i |
285 | + 1); |
286 | |
287 | is_last = (memcmp(p: skb->data + ether_packet_length, |
288 | q: HEADER_END_OF_USB_PACKET, |
289 | size: sizeof(HEADER_END_OF_USB_PACKET)) == 0); |
290 | if (!is_last) { |
291 | header_start = skb->data + ether_packet_length; |
292 | netdev_dbg(dev->net, |
293 | "End header: %6phC. Package length: %i\n" , |
294 | header_start, |
295 | skb->len - KALMIA_HEADER_LENGTH); |
296 | } |
297 | } |
298 | |
299 | if (is_last) { |
300 | skb2 = skb; |
301 | } |
302 | else { |
303 | skb2 = skb_clone(skb, GFP_ATOMIC); |
304 | if (unlikely(!skb2)) |
305 | return 0; |
306 | } |
307 | |
308 | skb_trim(skb: skb2, len: ether_packet_length); |
309 | |
310 | if (is_last) { |
311 | return 1; |
312 | } |
313 | else { |
314 | usbnet_skb_return(dev, skb2); |
315 | skb_pull(skb, len: ether_packet_length); |
316 | } |
317 | |
318 | i++; |
319 | } |
320 | while (skb->len); |
321 | |
322 | return 1; |
323 | } |
324 | |
325 | static const struct driver_info kalmia_info = { |
326 | .description = "Samsung Kalmia LTE USB dongle" , |
327 | .flags = FLAG_WWAN, |
328 | .bind = kalmia_bind, |
329 | .rx_fixup = kalmia_rx_fixup, |
330 | .tx_fixup = kalmia_tx_fixup |
331 | }; |
332 | |
333 | /*-------------------------------------------------------------------------*/ |
334 | |
335 | static const struct usb_device_id products[] = { |
336 | /* The unswitched USB ID, to get the module auto loaded: */ |
337 | { USB_DEVICE(0x04e8, 0x689a) }, |
338 | /* The stick switched into modem (by e.g. usb_modeswitch): */ |
339 | { USB_DEVICE(0x04e8, 0x6889), |
340 | .driver_info = (unsigned long) &kalmia_info, }, |
341 | { /* EMPTY == end of list */} }; |
342 | MODULE_DEVICE_TABLE( usb, products); |
343 | |
344 | static struct usb_driver kalmia_driver = { |
345 | .name = "kalmia" , |
346 | .id_table = products, |
347 | .probe = usbnet_probe, |
348 | .disconnect = usbnet_disconnect, |
349 | .suspend = usbnet_suspend, |
350 | .resume = usbnet_resume, |
351 | .disable_hub_initiated_lpm = 1, |
352 | }; |
353 | |
354 | module_usb_driver(kalmia_driver); |
355 | |
356 | MODULE_AUTHOR("Marius Bjoernstad Kotsbak <marius@kotsbak.com>" ); |
357 | MODULE_DESCRIPTION("Samsung Kalmia USB network driver" ); |
358 | MODULE_LICENSE("GPL" ); |
359 | |