umodem.c revision 1.25 1 /* $NetBSD: umodem.c,v 1.25 2000/03/27 12:33:57 augustss Exp $ */
2
3 /*
4 * Copyright (c) 1998 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Lennart Augustsson (augustss (at) carlstedt.se) at
9 * Carlstedt Research & Technology.
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. All advertising materials mentioning features or use of this software
20 * must display the following acknowledgement:
21 * This product includes software developed by the NetBSD
22 * Foundation, Inc. and its contributors.
23 * 4. Neither the name of The NetBSD Foundation nor the names of its
24 * contributors may be used to endorse or promote products derived
25 * from this software without specific prior written permission.
26 *
27 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
28 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
29 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
30 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
31 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
32 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
33 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
34 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
35 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
37 * POSSIBILITY OF SUCH DAMAGE.
38 */
39
40 /*
41 * Comm Class spec: http://www.usb.org/developers/data/usbcdc11.pdf
42 */
43
44 /*
45 * TODO:
46 * - Add error recovery in various places; the big problem is what
47 * to do in a callback if there is an error.
48 * - Implement a Call Device for modems without multiplexed commands.
49 *
50 */
51
52 #include <sys/param.h>
53 #include <sys/systm.h>
54 #include <sys/kernel.h>
55 #include <sys/ioctl.h>
56 #include <sys/conf.h>
57 #include <sys/tty.h>
58 #include <sys/file.h>
59 #include <sys/select.h>
60 #include <sys/proc.h>
61 #include <sys/vnode.h>
62 #include <sys/device.h>
63 #include <sys/poll.h>
64
65 #include <dev/usb/usb.h>
66 #include <dev/usb/usbcdc.h>
67
68 #include <dev/usb/usbdi.h>
69 #include <dev/usb/usbdi_util.h>
70 #include <dev/usb/usbdevs.h>
71 #include <dev/usb/usb_quirks.h>
72
73 #include <dev/usb/usbdevs.h>
74 #include <dev/usb/ucomvar.h>
75
76 #ifdef UMODEM_DEBUG
77 #define DPRINTFN(n, x) if (umodemdebug > (n)) logprintf x
78 int umodemdebug = 0;
79 #else
80 #define DPRINTFN(n, x)
81 #endif
82 #define DPRINTF(x) DPRINTFN(0, x)
83
84 struct umodem_softc {
85 USBBASEDEVICE sc_dev; /* base device */
86
87 usbd_device_handle sc_udev; /* USB device */
88
89 int sc_ctl_iface_no;
90 usbd_interface_handle sc_ctl_iface; /* control interface */
91 int sc_data_iface_no;
92 usbd_interface_handle sc_data_iface; /* data interface */
93
94 int sc_cm_cap; /* CM capabilities */
95 int sc_acm_cap; /* ACM capabilities */
96
97 int sc_cm_over_data;
98
99 usb_cdc_line_state_t sc_line_state; /* current line state */
100 u_char sc_dtr; /* current DTR state */
101 u_char sc_rts; /* current RTS state */
102
103 device_ptr_t sc_subdev; /* ucom device */
104
105 u_char sc_opening; /* lock during open */
106 u_char sc_dying; /* disconnecting */
107 };
108
109 Static void *umodem_get_desc
110 __P((usbd_device_handle dev, int type, int subtype));
111 Static usbd_status umodem_set_comm_feature
112 __P((struct umodem_softc *sc, int feature, int state));
113 Static usbd_status umodem_set_line_coding
114 __P((struct umodem_softc *sc, usb_cdc_line_state_t *state));
115
116 Static void umodem_get_caps __P((usbd_device_handle, int *, int *));
117
118 Static void umodem_get_status
119 __P((void *, int portno, u_char *lsr, u_char *msr));
120 Static void umodem_set __P((void *, int, int, int));
121 Static void umodem_dtr __P((struct umodem_softc *, int));
122 Static void umodem_rts __P((struct umodem_softc *, int));
123 Static void umodem_break __P((struct umodem_softc *, int));
124 Static void umodem_set_line_state __P((struct umodem_softc *));
125 Static int umodem_param __P((void *, int, struct termios *));
126 Static int umodem_ioctl __P((void *, int, u_long, caddr_t, int,
127 struct proc *));
128
129 Static struct ucom_methods umodem_methods = {
130 umodem_get_status,
131 umodem_set,
132 umodem_param,
133 umodem_ioctl,
134 NULL,
135 NULL,
136 };
137
138 USB_DECLARE_DRIVER(umodem);
139
140 USB_MATCH(umodem)
141 {
142 USB_MATCH_START(umodem, uaa);
143 usb_interface_descriptor_t *id;
144 int cm, acm;
145
146 if (uaa->iface == NULL)
147 return (UMATCH_NONE);
148
149 id = usbd_get_interface_descriptor(uaa->iface);
150 if (id == NULL ||
151 id->bInterfaceClass != UICLASS_CDC ||
152 id->bInterfaceSubClass != UISUBCLASS_ABSTRACT_CONTROL_MODEL ||
153 id->bInterfaceProtocol != UIPROTO_CDC_AT)
154 return (UMATCH_NONE);
155
156 umodem_get_caps(uaa->device, &cm, &acm);
157 if (!(cm & USB_CDC_CM_DOES_CM) ||
158 !(cm & USB_CDC_CM_OVER_DATA) ||
159 !(acm & USB_CDC_ACM_HAS_LINE))
160 return (UMATCH_NONE);
161
162 return (UMATCH_IFACECLASS_IFACESUBCLASS_IFACEPROTO);
163 }
164
165 USB_ATTACH(umodem)
166 {
167 USB_ATTACH_START(umodem, sc, uaa);
168 usbd_device_handle dev = uaa->device;
169 usb_interface_descriptor_t *id;
170 usb_endpoint_descriptor_t *ed;
171 usb_cdc_cm_descriptor_t *cmd;
172 char devinfo[1024];
173 usbd_status err;
174 int data_ifcno;
175 int i;
176 struct ucom_attach_args uca;
177
178 usbd_devinfo(uaa->device, 0, devinfo);
179 USB_ATTACH_SETUP;
180
181 sc->sc_udev = dev;
182 sc->sc_ctl_iface = uaa->iface;
183
184 id = usbd_get_interface_descriptor(sc->sc_ctl_iface);
185 printf("%s: %s, iclass %d/%d\n", USBDEVNAME(sc->sc_dev),
186 devinfo, id->bInterfaceClass, id->bInterfaceSubClass);
187 sc->sc_ctl_iface_no = id->bInterfaceNumber;
188
189 umodem_get_caps(dev, &sc->sc_cm_cap, &sc->sc_acm_cap);
190
191 /* Get the data interface no. */
192 cmd = umodem_get_desc(dev, UDESC_CS_INTERFACE, UDESCSUB_CDC_CM);
193 if (cmd == NULL) {
194 printf("%s: no CM descriptor\n", USBDEVNAME(sc->sc_dev));
195 goto bad;
196 }
197 sc->sc_data_iface_no = data_ifcno = cmd->bDataInterface;
198
199 printf("%s: data interface %d, has %sCM over data, has %sbreak\n",
200 USBDEVNAME(sc->sc_dev), data_ifcno,
201 sc->sc_cm_cap & USB_CDC_CM_OVER_DATA ? "" : "no ",
202 sc->sc_acm_cap & USB_CDC_ACM_HAS_BREAK ? "" : "no ");
203
204
205 /* Get the data interface too. */
206 for (i = 0; i < uaa->nifaces; i++) {
207 if (uaa->ifaces[i] != NULL) {
208 id = usbd_get_interface_descriptor(uaa->ifaces[i]);
209 if (id != NULL && id->bInterfaceNumber == data_ifcno) {
210 sc->sc_data_iface = uaa->ifaces[i];
211 uaa->ifaces[i] = NULL;
212 }
213 }
214 }
215 if (sc->sc_data_iface == NULL) {
216 printf("%s: no data interface\n", USBDEVNAME(sc->sc_dev));
217 goto bad;
218 }
219
220 /*
221 * Find the bulk endpoints.
222 * Iterate over all endpoints in the data interface and take note.
223 */
224 uca.bulkin = uca.bulkout = -1;
225
226 id = usbd_get_interface_descriptor(sc->sc_data_iface);
227 for (i = 0; i < id->bNumEndpoints; i++) {
228 ed = usbd_interface2endpoint_descriptor(sc->sc_data_iface, i);
229 if (ed == NULL) {
230 printf("%s: no endpoint descriptor for %d\n",
231 USBDEVNAME(sc->sc_dev), i);
232 goto bad;
233 }
234 if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN &&
235 (ed->bmAttributes & UE_XFERTYPE) == UE_BULK) {
236 uca.bulkin = ed->bEndpointAddress;
237 } else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT &&
238 (ed->bmAttributes & UE_XFERTYPE) == UE_BULK) {
239 uca.bulkout = ed->bEndpointAddress;
240 }
241 }
242
243 if (uca.bulkin == -1) {
244 printf("%s: Could not find data bulk in\n",
245 USBDEVNAME(sc->sc_dev));
246 goto bad;
247 }
248 if (uca.bulkout == -1) {
249 printf("%s: Could not find data bulk out\n",
250 USBDEVNAME(sc->sc_dev));
251 goto bad;
252 }
253
254 if (sc->sc_cm_cap & USB_CDC_CM_OVER_DATA) {
255 err = umodem_set_comm_feature(sc, UCDC_ABSTRACT_STATE,
256 UCDC_DATA_MULTIPLEXED);
257 if (err) {
258 printf("%s: could not set data multiplex mode\n",
259 USBDEVNAME(sc->sc_dev));
260 goto bad;
261 }
262 sc->sc_cm_over_data = 1;
263 }
264
265 sc->sc_dtr = -1;
266
267 uca.portno = UCOM_UNK_PORTNO;
268 /* bulkin, bulkout set above */
269 uca.device = sc->sc_udev;
270 uca.iface = sc->sc_data_iface;
271 uca.methods = &umodem_methods;
272 uca.arg = sc;
273
274 DPRINTF(("umodem_attach: sc=%p\n", sc));
275 sc->sc_subdev = config_found_sm(self, &uca, ucomprint, ucomsubmatch);
276
277 usbd_add_drv_event(USB_EVENT_DRIVER_ATTACH, sc->sc_udev,
278 USBDEV(sc->sc_dev));
279
280 USB_ATTACH_SUCCESS_RETURN;
281
282 bad:
283 sc->sc_dying = 1;
284 USB_ATTACH_ERROR_RETURN;
285 }
286
287 void
288 umodem_get_caps(dev, cm, acm)
289 usbd_device_handle dev;
290 int *cm, *acm;
291 {
292 usb_cdc_cm_descriptor_t *cmd;
293 usb_cdc_acm_descriptor_t *cad;
294
295 *cm = *acm = 0;
296
297 cmd = umodem_get_desc(dev, UDESC_CS_INTERFACE, UDESCSUB_CDC_CM);
298 if (cmd == NULL) {
299 DPRINTF(("umodem_get_desc: no CM desc\n"));
300 return;
301 }
302 *cm = cmd->bmCapabilities;
303
304 cad = umodem_get_desc(dev, UDESC_CS_INTERFACE, UDESCSUB_CDC_ACM);
305 if (cad == NULL) {
306 DPRINTF(("umodem_get_desc: no ACM desc\n"));
307 return;
308 }
309 *acm = cad->bmCapabilities;
310 }
311
312 void
313 umodem_get_status(addr, portno, lsr, msr)
314 void *addr;
315 int portno;
316 u_char *lsr, *msr;
317 {
318 DPRINTF(("umodem_get_status:\n"));
319
320 if (lsr != NULL)
321 *lsr = 0; /* XXX */
322 if (msr != NULL)
323 *msr = 0; /* XXX */
324 }
325
326 int
327 umodem_param(addr, portno, t)
328 void *addr;
329 int portno;
330 struct termios *t;
331 {
332 struct umodem_softc *sc = addr;
333 usbd_status err;
334 usb_cdc_line_state_t ls;
335
336 DPRINTF(("umodem_param: sc=%p\n", sc));
337
338 USETDW(ls.dwDTERate, t->c_ospeed);
339 if (ISSET(t->c_cflag, CSTOPB))
340 ls.bCharFormat = UCDC_STOP_BIT_2;
341 else
342 ls.bCharFormat = UCDC_STOP_BIT_1;
343 if (ISSET(t->c_cflag, PARENB)) {
344 if (ISSET(t->c_cflag, PARODD))
345 ls.bParityType = UCDC_PARITY_ODD;
346 else
347 ls.bParityType = UCDC_PARITY_EVEN;
348 } else
349 ls.bParityType = UCDC_PARITY_NONE;
350 switch (ISSET(t->c_cflag, CSIZE)) {
351 case CS5:
352 ls.bDataBits = 5;
353 break;
354 case CS6:
355 ls.bDataBits = 6;
356 break;
357 case CS7:
358 ls.bDataBits = 7;
359 break;
360 case CS8:
361 ls.bDataBits = 8;
362 break;
363 }
364
365 err = umodem_set_line_coding(sc, &ls);
366 if (err) {
367 DPRINTF(("umodem_param: err=%s\n", usbd_errstr(err)));
368 return (1);
369 }
370 return (0);
371 }
372
373 int
374 umodem_ioctl(addr, portno, cmd, data, flag, p)
375 void *addr;
376 int portno;
377 u_long cmd;
378 caddr_t data;
379 int flag;
380 struct proc *p;
381 {
382 struct umodem_softc *sc = addr;
383 int error = 0;
384
385 if (sc->sc_dying)
386 return (EIO);
387
388 DPRINTF(("umodemioctl: cmd=0x%08lx\n", cmd));
389
390 switch (cmd) {
391 case USB_GET_CM_OVER_DATA:
392 *(int *)data = sc->sc_cm_over_data;
393 break;
394
395 case USB_SET_CM_OVER_DATA:
396 if (*(int *)data != sc->sc_cm_over_data) {
397 /* XXX change it */
398 }
399 break;
400
401 default:
402 DPRINTF(("umodemioctl: unknown\n"));
403 error = ENOTTY;
404 break;
405 }
406
407 return (error);
408 }
409
410 void
411 umodem_dtr(sc, onoff)
412 struct umodem_softc *sc;
413 int onoff;
414 {
415 DPRINTF(("umodem_modem: onoff=%d\n", onoff));
416
417 if (sc->sc_dtr == onoff)
418 return;
419 sc->sc_dtr = onoff;
420
421 umodem_set_line_state(sc);
422 }
423
424 void
425 umodem_rts(sc, onoff)
426 struct umodem_softc *sc;
427 int onoff;
428 {
429 DPRINTF(("umodem_modem: onoff=%d\n", onoff));
430
431 if (sc->sc_rts == onoff)
432 return;
433 sc->sc_rts = onoff;
434
435 umodem_set_line_state(sc);
436 }
437
438 void
439 umodem_set_line_state(sc)
440 struct umodem_softc *sc;
441 {
442 usb_device_request_t req;
443 int ls;
444
445 ls = (sc->sc_dtr ? UCDC_LINE_DTR : 0) |
446 (sc->sc_rts ? UCDC_LINE_RTS : 0);
447 req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
448 req.bRequest = UCDC_SET_CONTROL_LINE_STATE;
449 USETW(req.wValue, ls);
450 USETW(req.wIndex, sc->sc_ctl_iface_no);
451 USETW(req.wLength, 0);
452
453 (void)usbd_do_request(sc->sc_udev, &req, 0);
454
455 }
456
457 void
458 umodem_break(sc, onoff)
459 struct umodem_softc *sc;
460 int onoff;
461 {
462 usb_device_request_t req;
463
464 DPRINTF(("umodem_break: onoff=%d\n", onoff));
465
466 if (!(sc->sc_acm_cap & USB_CDC_ACM_HAS_BREAK))
467 return;
468
469 req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
470 req.bRequest = UCDC_SEND_BREAK;
471 USETW(req.wValue, onoff ? UCDC_BREAK_ON : UCDC_BREAK_OFF);
472 USETW(req.wIndex, sc->sc_ctl_iface_no);
473 USETW(req.wLength, 0);
474
475 (void)usbd_do_request(sc->sc_udev, &req, 0);
476 }
477
478 void
479 umodem_set(addr, portno, reg, onoff)
480 void *addr;
481 int portno;
482 int onoff;
483 {
484 struct umodem_softc *sc = addr;
485
486 switch (reg) {
487 case UCOM_SET_DTR:
488 umodem_dtr(sc, onoff);
489 break;
490 case UCOM_SET_RTS:
491 umodem_rts(sc, onoff);
492 break;
493 case UCOM_SET_BREAK:
494 umodem_break(sc, onoff);
495 break;
496 default:
497 break;
498 }
499 }
500
501 usbd_status
502 umodem_set_line_coding(sc, state)
503 struct umodem_softc *sc;
504 usb_cdc_line_state_t *state;
505 {
506 usb_device_request_t req;
507 usbd_status err;
508
509 DPRINTF(("umodem_set_line_coding: rate=%d fmt=%d parity=%d bits=%d\n",
510 UGETDW(state->dwDTERate), state->bCharFormat,
511 state->bParityType, state->bDataBits));
512
513 if (memcmp(state, &sc->sc_line_state, UCDC_LINE_STATE_LENGTH) == 0) {
514 DPRINTF(("umodem_set_line_coding: already set\n"));
515 return (USBD_NORMAL_COMPLETION);
516 }
517
518 req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
519 req.bRequest = UCDC_SET_LINE_CODING;
520 USETW(req.wValue, 0);
521 USETW(req.wIndex, sc->sc_ctl_iface_no);
522 USETW(req.wLength, UCDC_LINE_STATE_LENGTH);
523
524 err = usbd_do_request(sc->sc_udev, &req, state);
525 if (err) {
526 DPRINTF(("umodem_set_line_coding: failed, err=%s\n",
527 usbd_errstr(err)));
528 return (err);
529 }
530
531 sc->sc_line_state = *state;
532
533 return (USBD_NORMAL_COMPLETION);
534 }
535
536 void *
537 umodem_get_desc(dev, type, subtype)
538 usbd_device_handle dev;
539 int type;
540 int subtype;
541 {
542 usb_descriptor_t *desc;
543 usb_config_descriptor_t *cd = usbd_get_config_descriptor(dev);
544 uByte *p = (uByte *)cd;
545 uByte *end = p + UGETW(cd->wTotalLength);
546
547 while (p < end) {
548 desc = (usb_descriptor_t *)p;
549 if (desc->bDescriptorType == type &&
550 desc->bDescriptorSubtype == subtype)
551 return (desc);
552 p += desc->bLength;
553 }
554
555 return (0);
556 }
557
558 usbd_status
559 umodem_set_comm_feature(sc, feature, state)
560 struct umodem_softc *sc;
561 int feature;
562 int state;
563 {
564 usb_device_request_t req;
565 usbd_status err;
566 usb_cdc_abstract_state_t ast;
567
568 DPRINTF(("umodem_set_comm_feature: feature=%d state=%d\n", feature,
569 state));
570
571 req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
572 req.bRequest = UCDC_SET_COMM_FEATURE;
573 USETW(req.wValue, feature);
574 USETW(req.wIndex, sc->sc_ctl_iface_no);
575 USETW(req.wLength, UCDC_ABSTRACT_STATE_LENGTH);
576 USETW(ast.wState, state);
577
578 err = usbd_do_request(sc->sc_udev, &req, &ast);
579 if (err) {
580 DPRINTF(("umodem_set_comm_feature: feature=%d, err=%s\n",
581 feature, usbd_errstr(err)));
582 return (err);
583 }
584
585 return (USBD_NORMAL_COMPLETION);
586 }
587
588 int
589 umodem_activate(self, act)
590 device_ptr_t self;
591 enum devact act;
592 {
593 struct umodem_softc *sc = (struct umodem_softc *)self;
594 int rv = 0;
595
596 switch (act) {
597 case DVACT_ACTIVATE:
598 return (EOPNOTSUPP);
599 break;
600
601 case DVACT_DEACTIVATE:
602 sc->sc_dying = 1;
603 if (sc->sc_subdev)
604 rv = config_deactivate(sc->sc_subdev);
605 break;
606 }
607 return (rv);
608 }
609
610 USB_DETACH(umodem)
611 {
612 USB_DETACH_START(umodem, sc);
613 int rv = 0;
614
615 DPRINTF(("umodem_detach: sc=%p flags=%d\n", sc, flags));
616
617 sc->sc_dying = 1;
618
619 if (sc->sc_subdev != NULL)
620 rv = config_detach(sc->sc_subdev, flags);
621
622 usbd_add_drv_event(USB_EVENT_DRIVER_DETACH, sc->sc_udev,
623 USBDEV(sc->sc_dev));
624
625 return (rv);
626 }
627