Commit | Line | Data |
---|---|---|
a700a71b SW |
1 | /* |
2 | * LG-P500 Smartphone | |
3 | * Written by Yellow Rabbit <yrabbit@sdf.lonestar.org> | |
4 | */ | |
5 | ||
6 | /* | |
7 | * XXX | |
8 | * USB: | |
9 | * Takes two interfaces. | |
10 | * IN and OUT endpoints on the data interface (altsetting). | |
11 | * Interrupt endpoint on the control interface. | |
12 | * | |
13 | * NET: | |
14 | * Transfer frames without modification (AS IS). | |
15 | */ | |
16 | ||
17 | #include <sys/param.h> | |
18 | #include <sys/systm.h> | |
19 | #include <sys/kernel.h> | |
20 | #include <sys/socket.h> | |
21 | #include <sys/sockio.h> | |
22 | #include <sys/bus.h> | |
23 | ||
24 | #include <net/if.h> | |
25 | #include <net/ifq_var.h> | |
26 | #include <net/if_arp.h> | |
27 | #include <net/ethernet.h> | |
28 | #include <net/bpf.h> | |
29 | ||
30 | #include <bus/usb/usb.h> | |
31 | #include <bus/usb/usbcdc.h> | |
32 | #include <bus/usb/usbdi.h> | |
33 | #include <bus/usb/usbdi_util.h> | |
34 | #include <bus/usb/usbdivar.h> | |
35 | #include <bus/usb/usb_ethersubr.h> | |
36 | ||
37 | #include "if_lgue.h" | |
38 | ||
39 | /* | |
40 | * Supported device vendors/products | |
41 | */ | |
42 | static struct usb_devno lgue_devs[] = { | |
9de4e294 | 43 | { USB_DEVICE(0x1004, 0x61a2) } /* LG P500 */ |
a700a71b SW |
44 | }; |
45 | ||
46 | static int lgue_match(device_t); | |
47 | static int lgue_attach(device_t); | |
48 | static int lgue_detach(device_t); | |
49 | ||
50 | static void lgue_start(struct ifnet *); | |
51 | static void lgue_stop(struct lgue_softc *); | |
52 | static void lgue_init(void *); | |
53 | static void lgue_watchdog(struct ifnet *); | |
54 | static int lgue_ioctl(struct ifnet *, u_long, caddr_t, struct ucred *); | |
55 | ||
56 | static int lgue_encap(struct lgue_softc *, struct mbuf *); | |
57 | static int lgue_start_transfer(struct lgue_softc *); | |
58 | ||
59 | static void lgue_txeof(usbd_xfer_handle, usbd_private_handle, usbd_status); | |
60 | ||
61 | static void lgue_start_ipifunc(void *); | |
62 | static void lgue_start_schedule(struct ifnet *); | |
63 | ||
64 | static int lgue_newbuf(struct lgue_softc *, int, struct mbuf **); | |
65 | static void lgue_rxstart(struct ifnet *); | |
66 | static void lgue_rxeof(usbd_xfer_handle, usbd_private_handle, usbd_status); | |
67 | ||
68 | static void lgue_intrstart(struct ifnet *); | |
69 | static void lgue_intreof(usbd_xfer_handle, usbd_private_handle, usbd_status); | |
70 | ||
71 | static int lgue_get_data_iface_no(usbd_device_handle, | |
72 | usb_interface_descriptor_t *); | |
73 | ||
74 | static int lgue_getmac(struct lgue_softc *, void *); | |
75 | static int lgue_getmtu(struct lgue_softc *); | |
76 | ||
77 | static int hex(char); | |
78 | ||
79 | static device_method_t lgue_methods[] = { | |
80 | DEVMETHOD(device_probe, lgue_match), | |
81 | DEVMETHOD(device_attach, lgue_attach), | |
82 | DEVMETHOD(device_detach, lgue_detach), | |
83 | ||
84 | { 0, 0 } | |
85 | }; | |
86 | ||
87 | static driver_t lgue_driver = { | |
88 | "lgue", | |
89 | lgue_methods, | |
90 | sizeof(struct lgue_softc) | |
91 | }; | |
92 | ||
93 | static devclass_t lgue_devclass; | |
94 | ||
95 | DECLARE_DUMMY_MODULE(if_lgue); | |
8515b7f9 | 96 | DRIVER_MODULE(lgue, uhub, lgue_driver, lgue_devclass, usbd_driver_load, NULL); |
a700a71b SW |
97 | MODULE_DEPEND(lgue, usb, 1, 1, 1); |
98 | ||
99 | /* | |
100 | * Probe chip | |
101 | */ | |
102 | static int | |
103 | lgue_match(device_t dev) | |
104 | { | |
105 | struct usb_attach_arg *uaa; | |
106 | usb_interface_descriptor_t *id; | |
107 | ||
108 | uaa = device_get_ivars(dev); | |
109 | if (uaa->iface == NULL) | |
110 | return(UMATCH_NONE); | |
111 | ||
112 | if (usb_lookup(lgue_devs, uaa->vendor, uaa->product) != NULL) { | |
113 | id = usbd_get_interface_descriptor(uaa->iface); | |
114 | if (id != NULL && | |
115 | id->bInterfaceClass == UICLASS_CDC && | |
116 | id->bInterfaceSubClass == UISUBCLASS_ETHERNET_NETWORKING_CONTROL_MODEL) | |
117 | return(UMATCH_VENDOR_PRODUCT); | |
118 | } | |
119 | return(UMATCH_NONE); | |
120 | } | |
121 | ||
122 | /* | |
123 | * Attach the interface. | |
124 | */ | |
125 | static int | |
126 | lgue_attach(device_t dev) | |
127 | { | |
128 | struct lgue_softc *sc; | |
129 | struct usb_attach_arg *uaa; | |
130 | struct ifnet *ifp; | |
131 | usb_interface_descriptor_t *id; | |
132 | usb_endpoint_descriptor_t *ed; | |
133 | int i; | |
134 | u_char eaddr[ETHER_ADDR_LEN]; | |
135 | usbd_status err; | |
136 | ||
137 | sc = device_get_softc(dev); | |
138 | uaa = device_get_ivars(dev); | |
139 | ||
140 | sc->lgue_ctl_iface = uaa->iface; | |
141 | sc->lgue_udev = uaa->device; | |
142 | ||
143 | /* It has only config but in case... */ | |
144 | if (usbd_set_config_no(sc->lgue_udev, LGUE_CONFIG_NO, 0)) { | |
145 | device_printf(dev, "setting config no %d failed\n", | |
146 | LGUE_CONFIG_NO); | |
147 | return(ENXIO); | |
148 | } | |
149 | ||
150 | /* Get control and data intefaces */ | |
151 | id = usbd_get_interface_descriptor(uaa->iface); | |
152 | sc->lgue_ctl_iface_no = id->bInterfaceNumber; | |
153 | sc->lgue_data_iface_no = lgue_get_data_iface_no(sc->lgue_udev, id); | |
154 | ||
155 | if (sc->lgue_data_iface_no == -1) { | |
156 | device_printf(dev, "no data interface number\n"); | |
157 | goto bad; | |
158 | } | |
159 | ||
160 | /* Claim data interface */ | |
161 | for (i = 0; i < uaa->nifaces; ++i) { | |
162 | if (uaa->ifaces[i] != NULL) { | |
163 | id = usbd_get_interface_descriptor(uaa->ifaces[i]); | |
164 | if (id != NULL && | |
165 | id->bInterfaceNumber == sc->lgue_data_iface_no) { | |
166 | err = usbd_set_interface(uaa->ifaces[i], | |
167 | LGUE_ALTERNATE_SETTING); | |
168 | if ( err != USBD_NORMAL_COMPLETION) { | |
169 | device_printf(dev, | |
170 | "no alternate data interface. err:%s\n", | |
171 | usbd_errstr(err)); | |
172 | goto bad; | |
173 | } | |
174 | sc->lgue_data_iface = uaa->ifaces[i]; | |
175 | uaa->ifaces[i] = NULL; | |
176 | } | |
177 | } | |
178 | } | |
179 | if (sc->lgue_data_iface == NULL) { | |
180 | device_printf(dev, "no data interface\n"); | |
181 | goto bad; | |
182 | } | |
183 | ||
184 | /* Find data interface endpoints */ | |
185 | id = usbd_get_interface_descriptor(sc->lgue_data_iface); | |
186 | sc->lgue_ed[LGUE_ENDPT_RX] = sc->lgue_ed[LGUE_ENDPT_TX] = -1; | |
187 | for (i = 0; i < id->bNumEndpoints; ++i) { | |
188 | ed = usbd_interface2endpoint_descriptor(sc->lgue_data_iface, i); | |
189 | if (!ed) { | |
190 | device_printf(dev, | |
191 | "couldn't get endpoint descriptor %d\n", i); | |
192 | goto bad; | |
193 | } | |
194 | if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN && | |
195 | UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) { | |
196 | sc->lgue_ed[LGUE_ENDPT_RX] = ed->bEndpointAddress; | |
197 | } else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT && | |
198 | UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) { | |
199 | sc->lgue_ed[LGUE_ENDPT_TX] = ed->bEndpointAddress; | |
200 | } | |
201 | } | |
202 | ||
203 | if (sc->lgue_ed[LGUE_ENDPT_RX] == -1) { | |
204 | device_printf(dev, "couldn't find data bilk in\n"); | |
205 | goto bad; | |
206 | } | |
207 | if (sc->lgue_ed[LGUE_ENDPT_TX] == -1) { | |
208 | device_printf(dev, "couldn't find data bilk out\n"); | |
209 | goto bad; | |
210 | } | |
211 | ||
212 | /* Find control interface endpoint */ | |
213 | id = usbd_get_interface_descriptor(sc->lgue_ctl_iface); | |
214 | sc->lgue_ed[LGUE_ENDPT_INTR] = -1; | |
215 | for (i = 0; i < id->bNumEndpoints; ++i) { | |
216 | ed = usbd_interface2endpoint_descriptor(sc->lgue_ctl_iface, i); | |
217 | if (!ed) { | |
218 | device_printf(dev, | |
219 | "couldn't get endpoint descriptor %d\n", i); | |
220 | goto bad; | |
221 | } | |
222 | if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN && | |
223 | UE_GET_XFERTYPE(ed->bmAttributes) == UE_INTERRUPT) { | |
224 | sc->lgue_ed[LGUE_ENDPT_INTR] = ed->bEndpointAddress; | |
225 | } | |
226 | } | |
227 | ||
228 | if (sc->lgue_ed[LGUE_ENDPT_INTR] == -1) { | |
229 | device_printf(dev, "couldn't find interrupt bilk in\n"); | |
230 | goto bad; | |
231 | } | |
232 | ||
233 | /* Create interface */ | |
234 | ifp = &sc->lgue_arpcom.ac_if; | |
235 | ifp->if_softc = sc; | |
236 | if_initname(ifp, device_get_name(dev), device_get_unit(dev)); | |
237 | lgue_getmac(sc, eaddr); | |
238 | ||
239 | ifp->if_mtu = lgue_getmtu(sc); | |
240 | ifp->if_data.ifi_mtu = ifp->if_mtu; | |
241 | ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; | |
242 | ifp->if_baudrate = 10000000; | |
243 | ifp->if_ioctl = lgue_ioctl; | |
244 | ifp->if_start = lgue_start; | |
245 | ifp->if_watchdog = lgue_watchdog; | |
246 | ifp->if_init = lgue_init; | |
247 | ifq_set_maxlen(&ifp->if_snd, IFQ_MAXLEN); | |
248 | ifq_set_ready(&ifp->if_snd); | |
249 | ||
250 | /* Call attach routine */ | |
251 | ether_ifattach(ifp, eaddr, NULL); | |
252 | usb_register_netisr(); | |
253 | sc->lgue_dying = 0; | |
254 | return(0); | |
255 | ||
256 | bad: | |
257 | return(ENXIO); | |
258 | } | |
259 | ||
260 | /* | |
261 | * Device detached. | |
262 | */ | |
263 | static int | |
264 | lgue_detach(device_t dev) | |
265 | { | |
266 | struct lgue_softc *sc; | |
267 | struct ifnet *ifp; | |
268 | ||
269 | sc = device_get_softc(dev); | |
270 | ifp = &sc->lgue_arpcom.ac_if; | |
271 | ether_ifdetach(ifp); | |
272 | ||
273 | if (sc->lgue_ep[LGUE_ENDPT_TX] != NULL) | |
274 | usbd_abort_pipe(sc->lgue_ep[LGUE_ENDPT_TX]); | |
275 | if (sc->lgue_ep[LGUE_ENDPT_RX] != NULL) | |
276 | usbd_abort_pipe(sc->lgue_ep[LGUE_ENDPT_RX]); | |
277 | if (sc->lgue_ep[LGUE_ENDPT_INTR] != NULL) | |
278 | usbd_abort_pipe(sc->lgue_ep[LGUE_ENDPT_INTR]); | |
279 | return(0); | |
280 | } | |
281 | ||
282 | /* | |
283 | * Find data interface. | |
284 | */ | |
285 | int | |
286 | lgue_get_data_iface_no(usbd_device_handle dev, usb_interface_descriptor_t *id) | |
287 | { | |
288 | const usb_cdc_union_descriptor_t *cud; | |
289 | ||
290 | cud = (const usb_cdc_union_descriptor_t *)usb_find_desc_if(dev, | |
291 | UDESC_CS_INTERFACE, UDESCSUB_CDC_UNION, id); | |
292 | return(cud ? cud->bSlaveInterface[0] : -1); | |
293 | } | |
294 | ||
295 | /* | |
296 | * Get hard max mtu | |
297 | */ | |
298 | static int | |
299 | lgue_getmtu(struct lgue_softc *sc) | |
300 | { | |
301 | const usb_cdc_ethernet_descriptor_t *ced; | |
302 | usb_interface_descriptor_t *id; | |
303 | ||
304 | id = usbd_get_interface_descriptor(sc->lgue_ctl_iface); | |
9de4e294 SW |
305 | if (id == NULL) { |
306 | kprintf("usbd_get_interface_descriptor() returned NULL\n"); | |
307 | return(ETHERMTU); | |
308 | } | |
309 | ||
a700a71b SW |
310 | ced = (const usb_cdc_ethernet_descriptor_t *)usb_find_desc_if(sc->lgue_udev, |
311 | UDESC_CS_INTERFACE, UDESCSUB_CDC_ETHERNET, id); | |
9de4e294 SW |
312 | if (ced == NULL) { |
313 | kprintf("usb_find_desc_if() returned NULL\n"); | |
314 | return(ETHERMTU); | |
315 | } | |
a700a71b | 316 | return(UGETW(ced->wMaxSegmentSize)); |
a700a71b SW |
317 | } |
318 | ||
319 | /* | |
320 | * Get mac address | |
321 | */ | |
322 | static int | |
323 | lgue_getmac(struct lgue_softc *sc, void *buf) | |
324 | { | |
325 | const usb_cdc_ethernet_descriptor_t *ced; | |
326 | usb_interface_descriptor_t *id; | |
327 | char sbuf[ETHER_ADDR_LEN * 2 + 1]; | |
328 | usbd_status err; | |
329 | int i; | |
330 | ||
331 | id = usbd_get_interface_descriptor(sc->lgue_ctl_iface); | |
332 | if (id == NULL) goto bad; | |
333 | ced = (const usb_cdc_ethernet_descriptor_t *)usb_find_desc_if(sc->lgue_udev, | |
334 | UDESC_CS_INTERFACE, UDESCSUB_CDC_ETHERNET, id); | |
335 | if (ced == NULL) goto bad; | |
336 | ||
337 | err = usbd_get_string(sc->lgue_udev, ced->iMACAddress, sbuf); | |
338 | if(err) { | |
339 | kprintf("Read MAC address failed\n"); | |
340 | goto bad; | |
341 | } | |
342 | ||
343 | for (i = 0; i < ETHER_ADDR_LEN; ++i) { | |
344 | ((uByte *)buf)[i] = (hex(sbuf[i * 2]) << 4) + hex(sbuf[(i * 2) + 1]); | |
345 | } | |
346 | return(0); | |
347 | bad: | |
348 | return(-1); | |
349 | } | |
350 | ||
351 | /* | |
352 | * Listen INTR pipe | |
353 | */ | |
354 | static void | |
355 | lgue_intrstart(struct ifnet *ifp) | |
356 | { | |
357 | struct lgue_softc *sc; | |
358 | ||
359 | sc = ifp->if_softc; | |
360 | usbd_setup_xfer(sc->lgue_intr_xfer, sc->lgue_ep[LGUE_ENDPT_INTR], sc, | |
361 | sc->lgue_intr_buf, LGUE_BUFSZ, | |
362 | USBD_SHORT_XFER_OK, USBD_NO_TIMEOUT, lgue_intreof); | |
363 | usbd_transfer(sc->lgue_intr_xfer); | |
364 | } | |
365 | ||
366 | /* | |
367 | * INTR arrived | |
368 | */ | |
369 | static void | |
370 | lgue_intreof(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) | |
371 | { | |
372 | struct ifnet *ifp; | |
373 | struct lgue_softc *sc; | |
374 | ||
375 | sc = priv; | |
376 | if (sc->lgue_dying) | |
377 | return; | |
378 | ||
379 | ifp = &sc->lgue_arpcom.ac_if; | |
380 | lwkt_serialize_enter(ifp->if_serializer); | |
381 | if (status != USBD_NORMAL_COMPLETION) { | |
b3ec9cec | 382 | if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) { |
a700a71b SW |
383 | lwkt_serialize_exit(ifp->if_serializer); |
384 | return; | |
b3ec9cec SW |
385 | } |
386 | if_printf(ifp, "usb error on intr: %s\n", usbd_errstr(status)); | |
a700a71b SW |
387 | if (status == USBD_STALLED) |
388 | usbd_clear_endpoint_stall(sc->lgue_ep[LGUE_ENDPT_INTR]); | |
b3ec9cec | 389 | lwkt_serialize_exit(ifp->if_serializer); |
a700a71b SW |
390 | return; |
391 | } | |
392 | lgue_intrstart(ifp); | |
393 | lwkt_serialize_exit(ifp->if_serializer); | |
394 | } | |
395 | ||
396 | /* | |
397 | * Encap packet & send | |
398 | */ | |
399 | static int | |
400 | lgue_encap(struct lgue_softc *sc, struct mbuf *m) | |
401 | { | |
402 | struct ifnet *ifp; | |
403 | struct lgue_queue_entry *entry; | |
404 | ||
405 | ifp = &sc->lgue_arpcom.ac_if; | |
406 | entry = kmalloc(sizeof(struct lgue_queue_entry), M_USBDEV , M_NOWAIT); | |
407 | if (entry == NULL) { | |
408 | if_printf(ifp, "no memory for internal queue entry\n"); | |
409 | return(ENOBUFS); | |
410 | } | |
411 | entry->entry_mbuf = m; | |
412 | ||
413 | /* Put packet into internal queue tail */ | |
414 | STAILQ_INSERT_TAIL(&sc->lgue_tx_queue, entry, entry_next); | |
415 | return(0); | |
416 | } | |
417 | ||
418 | /* | |
419 | * Start transfer from internal queue | |
420 | */ | |
421 | static int | |
422 | lgue_start_transfer(struct lgue_softc *sc) { | |
423 | usbd_status err; | |
424 | struct lgue_queue_entry *entry; | |
425 | struct ifnet *ifp; | |
426 | ||
427 | if (STAILQ_EMPTY(&sc->lgue_tx_queue)) | |
428 | return(0); | |
429 | ||
430 | ifp = &sc->lgue_arpcom.ac_if; | |
431 | entry = STAILQ_FIRST(&sc->lgue_tx_queue); | |
432 | STAILQ_REMOVE_HEAD(&sc->lgue_tx_queue, entry_next); | |
433 | ||
434 | m_copydata(entry->entry_mbuf, 0, entry->entry_mbuf->m_pkthdr.len, | |
435 | sc->lgue_tx_buf); | |
436 | ||
437 | /* Transmit */ | |
438 | usbd_setup_xfer(sc->lgue_tx_xfer, sc->lgue_ep[LGUE_ENDPT_TX], sc, | |
439 | sc->lgue_tx_buf, entry->entry_mbuf->m_pkthdr.len, | |
440 | USBD_SHORT_XFER_OK, USBD_NO_TIMEOUT, lgue_txeof); | |
441 | err = usbd_transfer(sc->lgue_tx_xfer); | |
442 | if (err != USBD_IN_PROGRESS) { | |
443 | m_freem(entry->entry_mbuf); | |
444 | kfree(entry, M_USBDEV); | |
445 | lgue_stop(sc); | |
446 | ifp->if_flags &= ~IFF_OACTIVE; | |
447 | return(EIO); | |
448 | } | |
449 | ||
450 | m_freem(entry->entry_mbuf); | |
451 | kfree(entry, M_USBDEV); | |
452 | ||
453 | sc->lgue_tx_cnt++; | |
454 | ifp->if_flags |= IFF_OACTIVE; | |
455 | ifp->if_timer = 5; | |
456 | return(0); | |
457 | } | |
458 | ||
459 | /* | |
460 | * Start call | |
461 | */ | |
462 | static void | |
463 | lgue_start_ipifunc(void *arg) | |
464 | { | |
465 | struct ifnet *ifp; | |
466 | struct lwkt_msg *lmsg; | |
467 | ||
468 | ifp = arg; | |
469 | lmsg = &ifp->if_start_nmsg[mycpuid].lmsg; | |
470 | crit_enter(); | |
471 | if (lmsg->ms_flags & MSGF_DONE) | |
006f2e1f | 472 | lwkt_sendmsg(netisr_portfn(mycpuid), lmsg); |
a700a71b SW |
473 | crit_exit(); |
474 | } | |
475 | ||
476 | /* | |
477 | * Schedule start call | |
478 | */ | |
479 | static void | |
480 | lgue_start_schedule(struct ifnet *ifp) | |
481 | { | |
a700a71b SW |
482 | int cpu; |
483 | ||
484 | cpu = ifp->if_start_cpuid(ifp); | |
485 | if (cpu != mycpuid) | |
486 | lwkt_send_ipiq(globaldata_find(cpu), lgue_start_ipifunc, ifp); | |
487 | else | |
a700a71b SW |
488 | lgue_start_ipifunc(ifp); |
489 | } | |
490 | ||
491 | /* | |
492 | * End of sending | |
493 | */ | |
494 | static void | |
495 | lgue_txeof(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) | |
496 | { | |
497 | struct ifnet *ifp; | |
498 | struct lgue_softc *sc; | |
499 | usbd_status err; | |
500 | ||
501 | sc = priv; | |
502 | if (sc->lgue_dying) | |
503 | return; | |
504 | ||
505 | ifp = &sc->lgue_arpcom.ac_if; | |
506 | ||
507 | if (status != USBD_NORMAL_COMPLETION) { | |
508 | if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) | |
509 | return; | |
510 | if (status == USBD_STALLED) | |
511 | usbd_clear_endpoint_stall(sc->lgue_ep[LGUE_ENDPT_TX]); | |
512 | return; | |
513 | } | |
514 | usbd_get_xfer_status(sc->lgue_tx_xfer, NULL, NULL, NULL,&err); | |
515 | if (err) | |
516 | ifp->if_oerrors++; | |
517 | else | |
518 | ifp->if_opackets++; | |
519 | ||
520 | if (!STAILQ_EMPTY(&sc->lgue_tx_queue)) { | |
521 | lgue_start_schedule(ifp); | |
522 | } | |
523 | ||
524 | ifp->if_timer = 0; | |
525 | ifp->if_flags &= ~IFF_OACTIVE; | |
526 | } | |
527 | ||
528 | /* | |
529 | * Start transfer | |
530 | */ | |
531 | static void | |
532 | lgue_start(struct ifnet *ifp) | |
533 | { | |
534 | struct lgue_softc *sc; | |
535 | struct mbuf *m_head; | |
536 | ||
537 | sc = ifp->if_softc; | |
538 | if (sc->lgue_dying) | |
539 | return; | |
540 | ||
541 | if (ifp->if_flags & IFF_OACTIVE) { | |
542 | return; | |
543 | } | |
544 | ||
545 | /* To internal queue */ | |
546 | while ((m_head = ifq_dequeue(&ifp->if_snd, NULL)) != NULL) { | |
547 | if (lgue_encap(sc, m_head)) { | |
548 | m_freem(m_head); | |
549 | break; | |
550 | } | |
551 | /* Filter */ | |
552 | BPF_MTAP(ifp, m_head); | |
553 | } | |
554 | ||
555 | lgue_start_transfer(sc); | |
556 | } | |
557 | ||
558 | /* | |
559 | * Stop | |
560 | */ | |
561 | static void | |
562 | lgue_stop(struct lgue_softc *sc) | |
563 | { | |
564 | struct ifnet *ifp; | |
565 | usbd_status err; | |
566 | struct lgue_queue_entry *entry; | |
567 | ||
568 | if (sc->lgue_dying) | |
569 | return; | |
570 | sc->lgue_dying = 1; | |
571 | ||
572 | ifp = &sc->lgue_arpcom.ac_if; | |
573 | ||
574 | /* Stop transfers */ | |
575 | if (sc->lgue_ep[LGUE_ENDPT_TX] != NULL) { | |
576 | err = usbd_abort_pipe(sc->lgue_ep[LGUE_ENDPT_TX]); | |
577 | if (err) { | |
578 | if_printf(ifp, "abort tx pipe failed:%s\n", | |
579 | usbd_errstr(err)); | |
580 | } | |
581 | err = usbd_close_pipe(sc->lgue_ep[LGUE_ENDPT_TX]); | |
582 | if (err) { | |
583 | if_printf(ifp, "close tx pipe failed:%s\n", | |
584 | usbd_errstr(err)); | |
585 | } | |
586 | } | |
587 | if (sc->lgue_ep[LGUE_ENDPT_RX] != NULL) { | |
588 | err = usbd_abort_pipe(sc->lgue_ep[LGUE_ENDPT_RX]); | |
589 | if (err) { | |
590 | if_printf(ifp, "abort rx pipe failed:%s\n", | |
591 | usbd_errstr(err)); | |
592 | } | |
593 | err = usbd_close_pipe(sc->lgue_ep[LGUE_ENDPT_RX]); | |
594 | if (err) { | |
595 | if_printf(ifp, "close rx pipe failed:%s\n", | |
596 | usbd_errstr(err)); | |
597 | } | |
598 | } | |
599 | if (sc->lgue_ep[LGUE_ENDPT_INTR] != NULL) { | |
600 | err = usbd_abort_pipe(sc->lgue_ep[LGUE_ENDPT_INTR]); | |
601 | if (err) { | |
602 | if_printf(ifp, "abort intr pipe failed:%s\n", | |
603 | usbd_errstr(err)); | |
604 | } | |
605 | err = usbd_close_pipe(sc->lgue_ep[LGUE_ENDPT_INTR]); | |
606 | if (err) { | |
607 | if_printf(ifp, "close intr pipe failed:%s\n", | |
608 | usbd_errstr(err)); | |
609 | } | |
610 | } | |
611 | ||
612 | /* Free tx buffers */ | |
613 | if (sc->lgue_tx_buf != NULL) { | |
614 | kfree(sc->lgue_tx_buf, M_USBDEV); | |
615 | sc->lgue_tx_buf = NULL; | |
616 | } | |
617 | if (sc->lgue_tx_xfer != NULL) { | |
618 | usbd_free_xfer(sc->lgue_tx_xfer); | |
619 | sc->lgue_tx_xfer = NULL; | |
620 | } | |
621 | ||
622 | /* Free rx buffers */ | |
623 | if (sc->lgue_rx_buf != NULL) { | |
624 | kfree(sc->lgue_rx_buf, M_USBDEV); | |
625 | sc->lgue_rx_buf = NULL; | |
626 | } | |
627 | if (sc->lgue_rx_xfer != NULL) { | |
628 | usbd_free_xfer(sc->lgue_rx_xfer); | |
629 | sc->lgue_rx_xfer = NULL; | |
630 | } | |
631 | ||
632 | /* Free intr buffer */ | |
633 | if (sc->lgue_intr_buf != NULL) { | |
634 | kfree(sc->lgue_intr_buf, M_USBDEV); | |
635 | sc->lgue_intr_buf = NULL; | |
636 | } | |
637 | if (sc->lgue_intr_xfer != NULL) { | |
638 | usbd_free_xfer(sc->lgue_intr_xfer); | |
639 | sc->lgue_intr_xfer = NULL; | |
640 | } | |
641 | ||
642 | /* Clear internal queue */ | |
643 | while (!STAILQ_EMPTY(&sc->lgue_tx_queue)) { | |
644 | entry = STAILQ_FIRST(&sc->lgue_tx_queue); | |
645 | STAILQ_REMOVE_HEAD(&sc->lgue_tx_queue, entry_next); | |
646 | m_freem(entry->entry_mbuf); | |
647 | kfree(entry, M_USBDEV); | |
648 | } | |
649 | ||
650 | ifp->if_flags &= ~(IFF_RUNNING | IFF_OACTIVE); | |
651 | } | |
652 | ||
653 | /* | |
654 | * Init | |
655 | */ | |
656 | static void | |
657 | lgue_init(void *xsc) | |
658 | { | |
659 | struct lgue_softc *sc; | |
660 | struct ifnet *ifp; | |
661 | usbd_status err; | |
662 | ||
663 | sc = xsc; | |
664 | ifp = &sc->lgue_arpcom.ac_if; | |
665 | ||
666 | if (ifp->if_flags & IFF_RUNNING) | |
667 | return; | |
668 | ||
669 | /* Create RX and TX bufs */ | |
670 | if (sc->lgue_tx_xfer == NULL) { | |
671 | sc->lgue_tx_xfer = usbd_alloc_xfer(sc->lgue_udev); | |
672 | if (sc->lgue_tx_xfer == NULL) { | |
673 | if_printf(ifp, "tx buffer allocate failed\n"); | |
674 | return; | |
675 | } | |
676 | } | |
677 | sc->lgue_tx_buf = kmalloc(LGUE_BUFSZ, M_USBDEV, M_WAITOK); | |
a700a71b SW |
678 | |
679 | if (sc->lgue_rx_xfer == NULL) { | |
680 | sc->lgue_rx_xfer = usbd_alloc_xfer(sc->lgue_udev); | |
681 | if (sc->lgue_rx_xfer == NULL) { | |
682 | if_printf(ifp, "rx buffer allocate failed\n"); | |
683 | return; | |
684 | } | |
685 | } | |
686 | sc->lgue_rx_buf = kmalloc(LGUE_BUFSZ, M_USBDEV, M_WAITOK); | |
a700a71b SW |
687 | |
688 | /* Create INTR buf */ | |
689 | if (sc->lgue_intr_xfer == NULL) { | |
690 | sc->lgue_intr_xfer = usbd_alloc_xfer(sc->lgue_udev); | |
691 | if (sc->lgue_intr_xfer == NULL) { | |
692 | if_printf(ifp, "intr buffer allocate failed\n"); | |
693 | return; | |
694 | } | |
695 | } | |
696 | sc->lgue_intr_buf = kmalloc(LGUE_BUFSZ, M_USBDEV, M_WAITOK); | |
697 | ||
698 | /* Open RX and TX pipes. */ | |
699 | err = usbd_open_pipe(sc->lgue_data_iface, sc->lgue_ed[LGUE_ENDPT_RX], | |
700 | USBD_EXCLUSIVE_USE, &sc->lgue_ep[LGUE_ENDPT_RX]); | |
701 | if (err) { | |
702 | if_printf(ifp, "open RX pipe failed: %s\n", usbd_errstr(err)); | |
703 | return; | |
704 | } | |
705 | err = usbd_open_pipe(sc->lgue_data_iface, sc->lgue_ed[LGUE_ENDPT_TX], | |
706 | USBD_EXCLUSIVE_USE, &sc->lgue_ep[LGUE_ENDPT_TX]); | |
707 | if (err) { | |
708 | if_printf(ifp, "open TX pipe failed: %s\n", usbd_errstr(err)); | |
709 | return; | |
710 | } | |
711 | /* Open INTR pipe. */ | |
712 | err = usbd_open_pipe(sc->lgue_ctl_iface, sc->lgue_ed[LGUE_ENDPT_INTR], | |
713 | USBD_EXCLUSIVE_USE, &sc->lgue_ep[LGUE_ENDPT_INTR]); | |
714 | if (err) { | |
715 | if_printf(ifp, "open INTR pipe failed: %s\n", usbd_errstr(err)); | |
716 | return; | |
717 | } | |
718 | ||
719 | /* Create internal queue */ | |
720 | STAILQ_INIT(&sc->lgue_tx_queue); | |
721 | ||
722 | ifp->if_flags |= IFF_RUNNING; | |
723 | ifp->if_flags &= ~IFF_OACTIVE; | |
724 | ||
725 | sc->lgue_dying = 0; | |
726 | ||
727 | lgue_rxstart(ifp); | |
728 | lgue_intrstart(ifp); | |
729 | } | |
730 | ||
731 | /* | |
732 | * New mbuf | |
733 | */ | |
734 | static int | |
735 | lgue_newbuf(struct lgue_softc *sc, int len, struct mbuf **m_buf) | |
736 | { | |
737 | struct ifnet *ifp; | |
738 | ||
739 | ifp = &sc->lgue_arpcom.ac_if; | |
740 | *m_buf = NULL; | |
741 | ||
742 | /* Allocate mbuf */ | |
743 | *m_buf = m_getcl(MB_DONTWAIT, MT_DATA, MT_HEADER); | |
744 | if (*m_buf == NULL) { | |
745 | if_printf(ifp, " no memory for rx buffer --- packet dropped!\n"); | |
746 | return(ENOBUFS); | |
747 | } | |
748 | (*m_buf)->m_len = (*m_buf)->m_pkthdr.len = MCLBYTES; | |
749 | m_adj(*m_buf, ETHER_ALIGN); | |
750 | return(0); | |
751 | } | |
752 | ||
753 | /* | |
754 | * Start read | |
755 | */ | |
756 | static void | |
757 | lgue_rxstart(struct ifnet *ifp) | |
758 | { | |
759 | struct lgue_softc *sc; | |
760 | ||
761 | sc = ifp->if_softc; | |
762 | if (sc->lgue_dying) | |
763 | return; | |
764 | ||
765 | usbd_setup_xfer(sc->lgue_rx_xfer, sc->lgue_ep[LGUE_ENDPT_RX], sc, | |
766 | sc->lgue_rx_buf, LGUE_BUFSZ, | |
767 | USBD_SHORT_XFER_OK, USBD_NO_TIMEOUT, lgue_rxeof); | |
768 | usbd_transfer(sc->lgue_rx_xfer); | |
769 | } | |
770 | ||
771 | /* | |
772 | * A frame has been uploaded: pass the resulting mbuf up to | |
773 | * the higher level protocols. | |
774 | */ | |
775 | static void | |
776 | lgue_rxeof(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) | |
777 | { | |
778 | struct lgue_softc *sc; | |
779 | struct mbuf *m; | |
780 | struct ifnet *ifp; | |
781 | int total_len; | |
782 | ||
783 | sc = priv; | |
784 | if (sc->lgue_dying) | |
785 | return; | |
786 | ||
787 | ifp = &sc->lgue_arpcom.ac_if; | |
788 | ||
789 | total_len = 0; | |
790 | ||
791 | if (!(ifp->if_flags & IFF_RUNNING)) | |
792 | return; | |
793 | ||
794 | if (status != USBD_NORMAL_COMPLETION) { | |
795 | if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) | |
796 | return; | |
797 | if (usbd_ratecheck(&sc->lgue_rx_notice)) { | |
798 | if_printf(ifp, "usb error on rx:%s\n", | |
799 | usbd_errstr(status)); | |
800 | } | |
801 | if (status == USBD_STALLED) | |
802 | usbd_clear_endpoint_stall(sc->lgue_ep[LGUE_ENDPT_RX]); | |
803 | goto done; | |
804 | } | |
805 | ||
806 | usbd_get_xfer_status(xfer, NULL, NULL, &total_len, NULL); | |
807 | ||
808 | if (total_len < sizeof(struct ether_header)) { | |
809 | ifp->if_ierrors++; | |
810 | goto done; | |
811 | } | |
812 | ||
813 | if (lgue_newbuf(sc, total_len, &m) == ENOBUFS) { | |
814 | ifp->if_ierrors++; | |
815 | return; | |
816 | } | |
817 | ||
818 | ifp->if_ipackets++; | |
819 | m_copyback(m, 0, total_len, sc->lgue_rx_buf); | |
820 | m->m_pkthdr.rcvif = ifp; | |
821 | m->m_pkthdr.len = m->m_len = total_len; | |
822 | ||
823 | usb_ether_input(m); | |
824 | lgue_rxstart(ifp); | |
825 | return; | |
826 | done: | |
827 | usbd_setup_xfer(sc->lgue_rx_xfer, sc->lgue_ep[LGUE_ENDPT_RX], sc, | |
828 | sc->lgue_rx_buf, LGUE_BUFSZ, | |
829 | USBD_SHORT_XFER_OK, USBD_NO_TIMEOUT, lgue_rxeof); | |
830 | usbd_transfer(sc->lgue_rx_xfer); | |
831 | } | |
832 | ||
833 | /* | |
834 | * Control | |
835 | */ | |
836 | static int | |
837 | lgue_ioctl(struct ifnet *ifp, u_long command, caddr_t data, struct ucred *cr) | |
838 | { | |
839 | struct lgue_softc *sc; | |
840 | struct ifreq *ifr; | |
841 | int err; | |
842 | ||
843 | err = 0; | |
844 | ifr = (struct ifreq *)data; | |
845 | sc = ifp->if_softc; | |
846 | ||
847 | switch(command) { | |
848 | case SIOCSIFFLAGS: | |
849 | if (ifp->if_flags & IFF_UP) { | |
850 | if (!(ifp->if_flags & IFF_RUNNING)) | |
851 | lgue_init(sc); | |
852 | } else if (ifp->if_flags & IFF_RUNNING) { | |
853 | lgue_stop(sc); | |
854 | } | |
855 | sc->lgue_if_flags = ifp->if_flags; | |
856 | err = 0; | |
857 | break; | |
858 | #if 0 | |
859 | case SIOCADDMULTI: | |
860 | case SIOCDELMULTI: | |
861 | err = 0; | |
862 | break; | |
863 | #endif | |
864 | case SIOCSIFMTU: | |
865 | ifp->if_mtu = ifr->ifr_mtu; | |
866 | break; | |
867 | default: | |
868 | err = ether_ioctl(ifp, command, data); | |
869 | break; | |
870 | } | |
871 | return(err); | |
872 | } | |
873 | ||
874 | /* | |
875 | * Watchdog | |
876 | */ | |
877 | static void | |
878 | lgue_watchdog(struct ifnet *ifp) | |
879 | { | |
a700a71b SW |
880 | ifp->if_oerrors++; |
881 | ||
882 | if (!ifq_is_empty(&ifp->if_snd)) | |
883 | lgue_start_schedule(ifp); | |
884 | } | |
885 | ||
886 | /* | |
887 | * Hex -> bin | |
888 | */ | |
889 | static int | |
890 | hex(char ch) | |
891 | { | |
892 | if ((ch >= 'a') && (ch <= 'f')) return (ch-'a'+10); | |
893 | if ((ch >= '0') && (ch <= '9')) return (ch-'0'); | |
894 | if ((ch >= 'A') && (ch <= 'F')) return (ch-'A'+10); | |
895 | return (-1); | |
896 | } |